blob: 37ca5137147374cfc0cbdf084d75561d4b768418 [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 1999 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Xerces" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation and was
* originally based on software copyright (c) 1999, International
* Business Machines, Inc., http://www.apache.org. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.xerces.utils;
/**
*
* @version
*/
public final class StringPool {
//
// Debugging
//
private static final boolean DEBUG_ADDITIONS = false;
/**
* Constants
*/
public static final int NULL_STRING = -1; // null
public static final int EMPTY_STRING = 0; // ""
/**
*
*/
public interface StringProducer {
/**
*
*/
public String toString(int offset, int length);
/**
*
*/
public void releaseString(int offset, int length);
/**
*
*/
public boolean equalsString(int offset, int length, char[] strChars, int strOffset, int strLength);
};
//
// Chunk size constants
//
private static final int INITIAL_CHUNK_SHIFT = 8; // 2^8 = 256
private static final int INITIAL_CHUNK_SIZE = (1 << INITIAL_CHUNK_SHIFT);
private static final int CHUNK_SHIFT = 13; // 2^13 = 8k
private static final int CHUNK_SIZE = (1 << CHUNK_SHIFT);
private static final int CHUNK_MASK = CHUNK_SIZE - 1;
private static final int INITIAL_CHUNK_COUNT = (1 << (16 - CHUNK_SHIFT)); // 2^16 = 64k
//
// Instance variables
//
//
// String and Symbol arrays
//
private int fStringCount = 0;
private int fStringFreeList = -1;
private String[][] fString = new String[INITIAL_CHUNK_COUNT][];
private StringPool.StringProducer[][] fStringProducer = new StringPool.StringProducer[INITIAL_CHUNK_COUNT][];
private int[][] fOffset = new int[INITIAL_CHUNK_COUNT][];
private int[][] fLength = new int[INITIAL_CHUNK_COUNT][];
private int[][] fCharsOffset = new int[INITIAL_CHUNK_COUNT][];
//
// String Lists
//
private int fStringListCount = 0;
private int fActiveStringList = -1;
private int[][] fStringList = new int[INITIAL_CHUNK_COUNT][];
//
// Symbol Hashtable
//
private static final int INITIAL_BUCKET_SIZE = 4;
private static final int HASHTABLE_SIZE = 128;
private int[][] fSymbolTable = new int[HASHTABLE_SIZE][];
//
// Symbol Cache
//
private SymbolCache fSymbolCache = null;
//
//
//
public StringPool() {
fSymbolCache = new SymbolCache();
if (addSymbol("") != EMPTY_STRING)
throw new RuntimeException("UTL002 cannot happen");
}
//
//
//
public void reset() {
int chunk = 0;
int index = 0;
for (int i = 0; i < fStringCount; i++) {
fString[chunk][index] = null;
if (fStringProducer[chunk][index] != null)
fStringProducer[chunk][index].releaseString(fOffset[chunk][index], fLength[chunk][index]);
fStringProducer[chunk][index] = null;
if (++index == CHUNK_SIZE) {
chunk++;
index = 0;
}
}
for (int i = 0; i < HASHTABLE_SIZE; i++)
fSymbolTable[i] = null;
fStringCount = 0;
fStringFreeList = -1;
fStringListCount = 0;
fActiveStringList = -1;
fSymbolCache.reset();
fShuffleCount = 0;
if (addSymbol("") != EMPTY_STRING)
throw new RuntimeException("UTL002 cannot happen");
}
//
// String interfaces
//
private boolean ensureCapacity(int chunk, int index) {
try {
return fOffset[chunk][index] == 0;
} catch (ArrayIndexOutOfBoundsException ex) {
if (index == 0) {
String[][] newString = new String[chunk * 2][];
System.arraycopy(fString, 0, newString, 0, chunk);
fString = newString;
StringPool.StringProducer[][] newProducer = new StringPool.StringProducer[chunk * 2][];
System.arraycopy(fStringProducer, 0, newProducer, 0, chunk);
fStringProducer = newProducer;
int[][] newInt = new int[chunk * 2][];
System.arraycopy(fOffset, 0, newInt, 0, chunk);
fOffset = newInt;
newInt = new int[chunk * 2][];
System.arraycopy(fLength, 0, newInt, 0, chunk);
fLength = newInt;
newInt = new int[chunk * 2][];
System.arraycopy(fCharsOffset, 0, newInt, 0, chunk);
fCharsOffset = newInt;
} else {
String[] newString = new String[index * 2];
System.arraycopy(fString[chunk], 0, newString, 0, index);
fString[chunk] = newString;
StringPool.StringProducer[] newProducer = new StringPool.StringProducer[index * 2];
System.arraycopy(fStringProducer[chunk], 0, newProducer, 0, index);
fStringProducer[chunk] = newProducer;
int[] newInt = new int[index * 2];
System.arraycopy(fOffset[chunk], 0, newInt, 0, index);
fOffset[chunk] = newInt;
newInt = new int[index * 2];
System.arraycopy(fLength[chunk], 0, newInt, 0, index);
fLength[chunk] = newInt;
newInt = new int[index * 2];
System.arraycopy(fCharsOffset[chunk], 0, newInt, 0, index);
fCharsOffset[chunk] = newInt;
return true;
}
} catch (NullPointerException ex) {
}
fString[chunk] = new String[INITIAL_CHUNK_SIZE];
fStringProducer[chunk] = new StringPool.StringProducer[INITIAL_CHUNK_SIZE];
fOffset[chunk] = new int[INITIAL_CHUNK_SIZE];
fLength[chunk] = new int[INITIAL_CHUNK_SIZE];
fCharsOffset[chunk] = new int[INITIAL_CHUNK_SIZE];
return true;
}
public int addString(String str) {
int chunk;
int index;
int stringIndex;
if (fStringFreeList != -1) {
stringIndex = fStringFreeList;
chunk = stringIndex >> CHUNK_SHIFT;
index = stringIndex & CHUNK_MASK;
fStringFreeList = fOffset[chunk][index];
} else {
stringIndex = fStringCount++;
chunk = stringIndex >> CHUNK_SHIFT;
index = stringIndex & CHUNK_MASK;
ensureCapacity(chunk, index);
}
fString[chunk][index] = str;
fStringProducer[chunk][index] = null;
fOffset[chunk][index] = 0;
fLength[chunk][index] = str.length();
fCharsOffset[chunk][index] = -1;
if (DEBUG_ADDITIONS)
System.err.println("addString(" + str + ") " + stringIndex);
return stringIndex;
}
public int addString(StringPool.StringProducer stringProducer, int offset, int length)
{
int chunk;
int index;
int stringIndex;
if (fStringFreeList != -1) {
stringIndex = fStringFreeList;
chunk = stringIndex >> CHUNK_SHIFT;
index = stringIndex & CHUNK_MASK;
fStringFreeList = fOffset[chunk][index];
} else {
stringIndex = fStringCount++;
chunk = stringIndex >> CHUNK_SHIFT;
index = stringIndex & CHUNK_MASK;
ensureCapacity(chunk, index);
}
fString[chunk][index] = null;
fStringProducer[chunk][index] = stringProducer;
fOffset[chunk][index] = offset;
fLength[chunk][index] = length;
fCharsOffset[chunk][index] = -1;
if (DEBUG_ADDITIONS)
System.err.println("addString(" + stringProducer.toString(offset, length) + ") " + stringIndex);
return stringIndex;
}
//
// Symbol interfaces
//
public SymbolCache getSymbolCache() {
return fSymbolCache;
}
//private static int fShuffleCount = 0;
private int fShuffleCount = 0;
public void resetShuffleCount() {
fShuffleCount = 0;
}
public void updateCacheLine(int symbolIndex, int totalMisses, int length) {
//System.err.println("found symbol " + toString(symbolIndex) + " after " + totalMisses + " total misses (" + (totalMisses/length) + " misses per character).");
if (++fShuffleCount > 200) {
// if (fShuffleCount == 201) System.out.println("Stopped shuffling...");
return;
}
// if ((fShuffleCount % 10) == 0) System.out.println("Shuffling pass " + fShuffleCount + " ...");
int chunk = symbolIndex >> CHUNK_SHIFT;
int index = symbolIndex & CHUNK_MASK;
int charsOffset = fCharsOffset[chunk][index];
fSymbolCache.updateCacheLine(charsOffset, totalMisses, length);
}
public int createNonMatchingSymbol(int startOffset,
int entry,
int[] entries,
int offset) throws Exception
{
int chunk;
int index;
int stringIndex;
if (fStringFreeList != -1) {
stringIndex = fStringFreeList;
chunk = stringIndex >> CHUNK_SHIFT;
index = stringIndex & CHUNK_MASK;
fStringFreeList = fOffset[chunk][index];
} else {
stringIndex = fStringCount++;
chunk = stringIndex >> CHUNK_SHIFT;
index = stringIndex & CHUNK_MASK;
ensureCapacity(chunk, index);
}
String str = fSymbolCache.createSymbol(stringIndex, startOffset, entry, entries, offset);
int slen = str.length();
fString[chunk][index] = str;
fStringProducer[chunk][index] = null;
fOffset[chunk][index] = -1;
fLength[chunk][index] = slen;
fCharsOffset[chunk][index] = startOffset;
int hashcode = StringHasher.hashString(str, slen);
int hc = hashcode % HASHTABLE_SIZE;
int[] bucket = fSymbolTable[hc];
hashSymbol(bucket, hashcode, chunk, index);
if (DEBUG_ADDITIONS)
System.err.println("addSymbolNew(" + str + ") " + stringIndex);
return stringIndex;
}
private void hashSymbol(int[] bucket, int hashcode, int chunk, int index) {
if (bucket == null) {
bucket = new int[1 + (INITIAL_BUCKET_SIZE * 3)];
bucket[0] = 1;
bucket[1] = hashcode;
bucket[2] = chunk;
bucket[3] = index;
int hc = hashcode % HASHTABLE_SIZE;
fSymbolTable[hc] = bucket;
} else {
int count = bucket[0];
int offset = 1 + (count * 3);
if (offset == bucket.length) {
int newSize = count + INITIAL_BUCKET_SIZE;
int[] newBucket = new int[1 + (newSize * 3)];
System.arraycopy(bucket, 0, newBucket, 0, offset);
bucket = newBucket;
int hc = hashcode % HASHTABLE_SIZE;
fSymbolTable[hc] = bucket;
}
bucket[offset++] = hashcode;
bucket[offset++] = chunk;
bucket[offset++] = index;
bucket[0] = ++count;
}
}
public int addSymbol(String str) {
int slen = str.length();
int hashcode = StringHasher.hashString(str, slen);
int hc = hashcode % HASHTABLE_SIZE;
int[] bucket = fSymbolTable[hc];
if (bucket != null) {
int j = 1;
for (int i = 0; i < bucket[0]; i++) {
if (bucket[j] == hashcode) {
int chunk = bucket[j+1];
int index = bucket[j+2];
if (slen == fLength[chunk][index]) {
int symoff = fCharsOffset[chunk][index];
boolean match = true;
char[] symbolChars = fSymbolCache.getSymbolChars();
for (int k = 0; k < slen; k++) {
if (symbolChars[symoff++] != str.charAt(k)) {
match = false;
break;
}
}
if (match) {
return (chunk << CHUNK_SHIFT) + index;
}
}
}
j += 3;
}
}
int chunk;
int index;
int stringIndex;
if (fStringFreeList != -1) {
stringIndex = fStringFreeList;
chunk = stringIndex >> CHUNK_SHIFT;
index = stringIndex & CHUNK_MASK;
fStringFreeList = fOffset[chunk][index];
} else {
stringIndex = fStringCount++;
chunk = stringIndex >> CHUNK_SHIFT;
index = stringIndex & CHUNK_MASK;
ensureCapacity(chunk, index);
}
fString[chunk][index] = str;
fStringProducer[chunk][index] = null;
fOffset[chunk][index] = -1;
fLength[chunk][index] = slen;
fCharsOffset[chunk][index] = fSymbolCache.addSymbolToCache(str, slen, stringIndex);
hashSymbol(bucket, hashcode, chunk, index);
if (DEBUG_ADDITIONS)
System.err.println("addSymbolNew(" + str + ") " + stringIndex);
return stringIndex;
}
public int addSymbol(StringPool.StringProducer stringProducer, int offset, int length, int hashcode) {
int hc = hashcode % HASHTABLE_SIZE;
int[] bucket = fSymbolTable[hc];
if (bucket != null) {
int j = 1;
for (int i = 0; i < bucket[0]; i++) {
if (bucket[j] == hashcode) {
int chunk = bucket[j+1];
int index = bucket[j+2];
char[] symbolChars = fSymbolCache.getSymbolChars();
if (stringProducer.equalsString(offset, length, symbolChars, fCharsOffset[chunk][index], fLength[chunk][index])) {
stringProducer.releaseString(offset, length);
return (chunk << CHUNK_SHIFT) + index;
}
}
j += 3;
}
}
int chunk;
int index;
int stringIndex;
if (fStringFreeList != -1) {
stringIndex = fStringFreeList;
chunk = stringIndex >> CHUNK_SHIFT;
index = stringIndex & CHUNK_MASK;
fStringFreeList = fOffset[chunk][index];
} else {
stringIndex = fStringCount++;
chunk = stringIndex >> CHUNK_SHIFT;
index = stringIndex & CHUNK_MASK;
ensureCapacity(chunk, index);
}
String str = stringProducer.toString(offset, length);
stringProducer.releaseString(offset, length);
int slen = str.length();
fString[chunk][index] = str;
fStringProducer[chunk][index] = null;
fOffset[chunk][index] = -1;
fLength[chunk][index] = slen;
fCharsOffset[chunk][index] = fSymbolCache.addSymbolToCache(str, slen, stringIndex);
hashSymbol(bucket, hashcode, chunk, index);
if (DEBUG_ADDITIONS)
System.err.println("addSymbol(" + str + ") " + stringIndex);
return stringIndex;
}
public int lookupSymbol(StringPool.StringProducer stringProducer, int offset, int length, int hashcode) {
int hc = hashcode % HASHTABLE_SIZE;
int[] bucket = fSymbolTable[hc];
if (bucket != null) {
int j = 1;
for (int i = 0; i < bucket[0]; i++) {
if (bucket[j] == hashcode) {
int chunk = bucket[j+1];
int index = bucket[j+2];
char[] symbolChars = fSymbolCache.getSymbolChars();
if (stringProducer.equalsString(offset, length, symbolChars, fCharsOffset[chunk][index], fLength[chunk][index])) {
return (chunk << CHUNK_SHIFT) + index;
}
}
j += 3;
}
}
return -1;
}
public int addNewSymbol(String str, int hashcode) {
int hc = hashcode % HASHTABLE_SIZE;
int[] bucket = fSymbolTable[hc];
int chunk;
int index;
int stringIndex;
if (fStringFreeList != -1) {
stringIndex = fStringFreeList;
chunk = stringIndex >> CHUNK_SHIFT;
index = stringIndex & CHUNK_MASK;
fStringFreeList = fOffset[chunk][index];
} else {
stringIndex = fStringCount++;
chunk = stringIndex >> CHUNK_SHIFT;
index = stringIndex & CHUNK_MASK;
ensureCapacity(chunk, index);
}
int slen = str.length();
fString[chunk][index] = str;
fStringProducer[chunk][index] = null;
fOffset[chunk][index] = -1;
fLength[chunk][index] = slen;
fCharsOffset[chunk][index] = fSymbolCache.addSymbolToCache(str, slen, stringIndex);
hashSymbol(bucket, hashcode, chunk, index);
if (DEBUG_ADDITIONS)
System.err.println("addSymbolNew(" + str + ") " + stringIndex);
return stringIndex;
}
public int addSymbol(int stringIndex) {
if (stringIndex < 0 || stringIndex >= fStringCount)
return -1;
int chunk = stringIndex >> CHUNK_SHIFT;
int index = stringIndex & CHUNK_MASK;
if (fOffset[chunk][index] == -1)
return stringIndex;
String s = fString[chunk][index];
if (s == null) {
s = fStringProducer[chunk][index].toString(fOffset[chunk][index], fLength[chunk][index]);
fStringProducer[chunk][index].releaseString(fOffset[chunk][index], fLength[chunk][index]);
fString[chunk][index] = s;
fStringProducer[chunk][index] = null;
}
return addSymbol(s);
}
//
// Get characters for defined symbols
//
public class CharArrayRange {
public char[] chars;
public int offset;
public int length;
}
public CharArrayRange createCharArrayRange() {
return new CharArrayRange();
}
public void getCharArrayRange(int symbolIndex, CharArrayRange r) {
if (symbolIndex < 0 || symbolIndex >= fStringCount) {
r.chars = null;
r.offset = -1;
r.length = -1;
return;
}
int chunk = symbolIndex >> CHUNK_SHIFT;
int index = symbolIndex & CHUNK_MASK;
r.chars = fSymbolCache.getSymbolChars();
r.offset = fCharsOffset[chunk][index];
r.length = fLength[chunk][index];
}
public boolean equalNames(int stringIndex1, int stringIndex2) {
if (stringIndex1 == stringIndex2)
return true;
return false;
}
//
// String list support
//
private boolean ensureListCapacity(int chunk, int index) {
try {
return fStringList[chunk][index] == 0;
} catch (ArrayIndexOutOfBoundsException ex) {
if (index == 0) {
int[][] newInt = new int[chunk * 2][];
System.arraycopy(fStringList, 0, newInt, 0, chunk);
fStringList = newInt;
} else {
int[] newInt = new int[index * 2];
System.arraycopy(fStringList[chunk], 0, newInt, 0, index);
fStringList[chunk] = newInt;
return true;
}
} catch (NullPointerException ex) {
}
fStringList[chunk] = new int[INITIAL_CHUNK_SIZE];
return true;
}
public int startStringList() {
fActiveStringList = fStringListCount;
return fStringListCount;
}
public boolean addStringToList(int stringListIndex, int stringIndex) {
if (stringIndex == -1 || stringListIndex != fActiveStringList)
return false;
int chunk = fStringListCount >> CHUNK_SHIFT;
int index = fStringListCount & CHUNK_MASK;
ensureListCapacity(chunk, index);
fStringList[chunk][index] = stringIndex;
fStringListCount++;
return true;
}
public void finishStringList(int stringListIndex) {
if (stringListIndex != fActiveStringList)
return;
int chunk = fStringListCount >> CHUNK_SHIFT;
int index = fStringListCount & CHUNK_MASK;
ensureListCapacity(chunk, index);
fStringList[chunk][index] = -1;
fActiveStringList = -1;
fStringListCount++;
}
public int stringListLength(int stringListIndex) {
int chunk = stringListIndex >> CHUNK_SHIFT;
int index = stringListIndex & CHUNK_MASK;
int count = 0;
while (true) {
if (fStringList[chunk][index] == -1)
return count;
count++;
if (++index == CHUNK_SIZE) {
chunk++;
index = 0;
}
}
}
public boolean stringInList(int stringListIndex, int stringIndex) {
int chunk = stringListIndex >> CHUNK_SHIFT;
int index = stringListIndex & CHUNK_MASK;
while (true) {
if (fStringList[chunk][index] == stringIndex)
return true;
if (fStringList[chunk][index] == -1)
return false;
if (++index == CHUNK_SIZE) {
chunk++;
index = 0;
}
}
}
public String stringListAsString(int stringListIndex) {
int chunk = stringListIndex >> CHUNK_SHIFT;
int index = stringListIndex & CHUNK_MASK;
StringBuffer sb = new StringBuffer();
char sep = '(';
while (fStringList[chunk][index] != -1) {
sb.append(sep);
sep = '|';
sb.append(toString(fStringList[chunk][index]));
if (++index == CHUNK_SIZE) {
chunk++;
index = 0;
}
}
if (sep == '|')
sb.append(')');
return sb.toString();
}
public int[] stringListAsIntArray(int stringListIndex) {
int chunk = stringListIndex >> CHUNK_SHIFT;
int index = stringListIndex & CHUNK_MASK;
int len = stringListLength(stringListIndex);
int[] ia = new int[len];
for (int i=0; i<len; i++) {
ia[i] = fStringList[chunk][index];
if (++index == CHUNK_SIZE) {
chunk++;
index = 0;
}
}
return ia;
}
//
//
//
private void releaseStringInternal(int chunk, int index) {
fString[chunk][index] = null;
fStringProducer[chunk][index] = null;
fLength[chunk][index] = 0;
//
// REVISIT - not synchronized.
//
fOffset[chunk][index] = fStringFreeList;
int offset = (chunk << CHUNK_SHIFT) + index;
fStringFreeList = offset;
}
//
//
//
public void releaseString(int stringIndex) {
if (stringIndex < 0 || stringIndex >= fStringCount)
return;
int chunk = stringIndex >> CHUNK_SHIFT;
int index = stringIndex & CHUNK_MASK;
if (fOffset[chunk][index] != -1) {
if (fStringProducer[chunk][index] != null)
fStringProducer[chunk][index].releaseString(fOffset[chunk][index], fLength[chunk][index]);
releaseStringInternal(chunk, index);
}
}
//
// Get String value. Cache the result.
//
public String toString(int stringIndex) {
if (stringIndex >= 0 && stringIndex < fString[0].length) {
String result = fString[0][stringIndex];
if (result != null) {
return result;
}
}
if (stringIndex < 0 || stringIndex >= fStringCount)
return null;
int chunk = stringIndex >> CHUNK_SHIFT;
int index = stringIndex & CHUNK_MASK;
String s = fString[chunk][index];
if (s != null)
return s;
s = fStringProducer[chunk][index].toString(fOffset[chunk][index], fLength[chunk][index]);
fStringProducer[chunk][index].releaseString(fOffset[chunk][index], fLength[chunk][index]);
fString[chunk][index] = s;
fStringProducer[chunk][index] = null;
return s;
}
//
//
//
public String orphanString(int stringIndex) {
if (stringIndex < 0 || stringIndex >= fStringCount)
return null;
int chunk = stringIndex >> CHUNK_SHIFT;
int index = stringIndex & CHUNK_MASK;
String s = fString[chunk][index];
if (s == null) {
s = fStringProducer[chunk][index].toString(fOffset[chunk][index], fLength[chunk][index]);
fStringProducer[chunk][index].releaseString(fOffset[chunk][index], fLength[chunk][index]);
releaseStringInternal(chunk, index);
} else if (fOffset[chunk][index] != -1) {
releaseStringInternal(chunk, index);
}
return s;
}
}