blob: e2581880e2185da6126774c5304137ea03924868 [file] [log] [blame]
/* Copyright 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.
*/
package org.apache.xmlbeans.impl.store;
import java.io.PrintStream;
import java.lang.ref.SoftReference;
public final class CharUtil {
public CharUtil(int charBufSize) {
_charBufSize = charBufSize;
}
public CharIterator getCharIterator(Object src, int off, int cch) {
_charIter.init(src, off, cch);
return _charIter;
}
public CharIterator getCharIterator(Object src, int off, int cch, int start) {
_charIter.init(src, off, cch, start);
return _charIter;
}
public static CharUtil getThreadLocalCharUtil() {
SoftReference<CharUtil> softRef = tl_charUtil.get();
CharUtil charUtil = softRef.get();
if (charUtil == null) {
charUtil = new CharUtil(CHARUTIL_INITIAL_BUFSIZE);
tl_charUtil.set(new SoftReference<>(charUtil));
}
return charUtil;
}
public static void getString(StringBuffer sb, Object src, int off, int cch) {
assert isValid(src, off, cch);
if (cch == 0) {
return;
}
if (src instanceof char[]) {
sb.append((char[]) src, off, cch);
} else if (src instanceof String) {
String s = (String) src;
if (off == 0 && cch == s.length()) {
sb.append(s);
} else {
sb.append(s, off, off + cch);
}
} else {
((CharJoin) src).getString(sb, off, cch);
}
}
public static void getChars(char[] chars, int start, Object src, int off, int cch) {
assert isValid(src, off, cch);
assert chars != null && start >= 0 && start <= chars.length;
if (cch == 0) {
return;
}
if (src instanceof char[]) {
char[] cs = (char[]) src;
System.arraycopy(cs, off, chars, start, cch);
} else if (src instanceof String) {
((String) src).getChars(off, off + cch, chars, start);
} else {
((CharJoin) src).getChars(chars, start, off, cch);
}
}
public static String getString(Object src, int off, int cch) {
assert isValid(src, off, cch);
if (cch == 0) {
return "";
}
if (src instanceof char[]) {
return new String((char[]) src, off, cch);
}
if (src instanceof String) {
String s = (String) src;
if (off == 0 && cch == s.length()) {
return s;
}
return s.substring(off, off + cch);
}
StringBuffer sb = new StringBuffer();
((CharJoin) src).getString(sb, off, cch);
return sb.toString();
}
public static boolean isWhiteSpace(char ch) {
switch (ch) {
case ' ':
case '\t':
case '\n':
case '\r':
return true;
default:
return false;
}
}
public final boolean isWhiteSpace(Object src, int off, int cch) {
assert isValid(src, off, cch);
if (cch <= 0) {
return true;
}
if (src instanceof char[]) {
for (char[] chars = (char[]) src; cch > 0; cch--) {
if (!isWhiteSpace(chars[off++])) {
return false;
}
}
return true;
}
if (src instanceof String) {
for (String s = (String) src; cch > 0; cch--) {
if (!isWhiteSpace(s.charAt(off++))) {
return false;
}
}
return true;
}
boolean isWhite = true;
for (_charIter.init(src, off, cch); _charIter.hasNext(); ) {
if (!isWhiteSpace(_charIter.next())) {
isWhite = false;
break;
}
}
_charIter.release();
return isWhite;
}
public Object stripLeft(Object src, int off, int cch) {
assert isValid(src, off, cch);
if (cch > 0) {
if (src instanceof char[]) {
char[] chars = (char[]) src;
while (cch > 0 && isWhiteSpace(chars[off])) {
cch--;
off++;
}
} else if (src instanceof String) {
String s = (String) src;
while (cch > 0 && isWhiteSpace(s.charAt(off))) {
cch--;
off++;
}
} else {
int count = 0;
for (_charIter.init(src, off, cch); _charIter.hasNext(); count++) {
if (!isWhiteSpace(_charIter.next())) {
break;
}
}
_charIter.release();
off += count;
}
}
if (cch == 0) {
_offSrc = 0;
_cchSrc = 0;
return null;
}
_offSrc = off;
_cchSrc = cch;
return src;
}
public Object stripRight(Object src, int off, int cch) {
assert isValid(src, off, cch);
if (cch > 0) {
for (_charIter.init(src, off, cch, cch); _charIter.hasPrev(); cch--) {
if (!isWhiteSpace(_charIter.prev())) {
break;
}
}
_charIter.release();
}
if (cch == 0) {
_offSrc = 0;
_cchSrc = 0;
return null;
}
_offSrc = off;
_cchSrc = cch;
return src;
}
public Object insertChars(
int posInsert,
Object src, int off, int cch,
Object srcInsert, int offInsert, int cchInsert) {
assert isValid(src, off, cch);
assert isValid(srcInsert, offInsert, cchInsert);
assert posInsert >= 0 && posInsert <= cch;
// TODO - at some point, instead of creating joins, I should
// normalize all the text into a single buffer to stop large
// tree's from being built when many modifications happen...
// TODO - actually, I should see if the size of the new char
// sequence is small enough to simply allocate a new contigous
// sequence, either in a common char[] managed by the master,
// or just create a new string ... this goes for remove chars
// as well.
if (cchInsert == 0) {
_cchSrc = cch;
_offSrc = off;
return src;
}
if (cch == 0) {
_cchSrc = cchInsert;
_offSrc = offInsert;
return srcInsert;
}
_cchSrc = cch + cchInsert;
Object newSrc;
if (_cchSrc <= MAX_COPY && canAllocate(_cchSrc)) {
char[] c = allocate(_cchSrc);
getChars(c, _offSrc, src, off, posInsert);
getChars(c, _offSrc + posInsert, srcInsert, offInsert, cchInsert);
getChars(c, _offSrc + posInsert + cchInsert, src, off + posInsert, cch - posInsert);
newSrc = c;
} else {
_offSrc = 0;
CharJoin newJoin;
if (posInsert == 0) {
newJoin = new CharJoin(srcInsert, offInsert, cchInsert, src, off);
} else if (posInsert == cch) {
newJoin = new CharJoin(src, off, cch, srcInsert, offInsert);
} else {
CharJoin j = new CharJoin(src, off, posInsert, srcInsert, offInsert);
newJoin = new CharJoin(j, 0, posInsert + cchInsert, src, off + posInsert);
}
if (newJoin._depth > CharJoin.MAX_DEPTH) {
newSrc = saveChars(newJoin, _offSrc, _cchSrc);
} else {
newSrc = newJoin;
}
}
assert isValid(newSrc, _offSrc, _cchSrc);
return newSrc;
}
public Object removeChars(int posRemove, int cchRemove, Object src, int off, int cch) {
assert isValid(src, off, cch);
assert posRemove >= 0 && posRemove <= cch;
assert cchRemove >= 0 && posRemove + cchRemove <= cch;
Object newSrc;
_cchSrc = cch - cchRemove;
if (_cchSrc == 0) {
newSrc = null;
_offSrc = 0;
} else if (posRemove == 0) {
newSrc = src;
_offSrc = off + cchRemove;
} else if (posRemove + cchRemove == cch) {
newSrc = src;
_offSrc = off;
} else {
int cchAfter = cch - cchRemove;
if (cchAfter <= MAX_COPY && canAllocate(cchAfter)) {
char[] chars = allocate(cchAfter);
getChars(chars, _offSrc, src, off, posRemove);
getChars(
chars, _offSrc + posRemove,
src, off + posRemove + cchRemove, cch - posRemove - cchRemove);
newSrc = chars;
// _offSrc = _offSrc;
} else {
CharJoin j = new CharJoin(src, off, posRemove, src, off + posRemove + cchRemove);
if (j._depth > CharJoin.MAX_DEPTH) {
newSrc = saveChars(j, 0, _cchSrc);
} else {
newSrc = j;
_offSrc = 0;
}
}
}
assert isValid(newSrc, _offSrc, _cchSrc);
return newSrc;
}
private boolean canAllocate(int cch) {
return _currentBuffer == null || _currentBuffer.length - _currentOffset >= cch;
}
private char[] allocate(int cch) {
assert _currentBuffer == null || _currentBuffer.length - _currentOffset > 0;
if (_currentBuffer == null) {
_currentBuffer = new char[Math.max(cch, _charBufSize)];
_currentOffset = 0;
}
_offSrc = _currentOffset;
_cchSrc = Math.min(_currentBuffer.length - _currentOffset, cch);
char[] retBuf = _currentBuffer;
assert _currentOffset + _cchSrc <= _currentBuffer.length;
if ((_currentOffset += _cchSrc) == _currentBuffer.length) {
_currentBuffer = null;
_currentOffset = 0;
}
return retBuf;
}
public Object saveChars(Object srcSave, int offSave, int cchSave) {
return saveChars(srcSave, offSave, cchSave, null, 0, 0);
}
public Object saveChars(
Object srcSave, int offSave, int cchSave,
Object srcPrev, int offPrev, int cchPrev) {
// BUGBUG (ericvas)
//
// There is a severe degenerate situation which can deveol here. The cases is where
// there is a long strings of calls to saveChars, where the caller passes in prev text
// to be prepended. In this cases, the buffer breaks and a join is made, but because the
// join is created, subsequent calls willproduce additional joins. I need to figure
// out a way that a whole bunch of joins are not created. I really only want to create
// joins in situations where large amount of text is manipulated.
assert isValid(srcSave, offSave, cchSave);
assert isValid(srcPrev, offPrev, cchPrev);
// Allocate some space to save the text and copy it there. This may not allocate all
// the space I need. This happens when I run out of buffer space. Deal with this later.
char[] srcAlloc = allocate(cchSave);
int offAlloc = _offSrc;
int cchAlloc = _cchSrc;
assert cchAlloc <= cchSave;
getChars(srcAlloc, offAlloc, srcSave, offSave, cchAlloc);
Object srcNew;
int offNew;
int cchNew = cchAlloc + cchPrev;
// The prev arguments specify a chunk of text which the caller wants prepended to the
// text to be saved. The optimization here is to detect the case where the prev text
// and the newly allcoated and saved text are adjacent, so that I can avoid copying
// or joining the two pieces. The situation where this happens most is when a parser
// reports a big piece of text in chunks, perhaps because there are entities in the
// big chunk of text.
CharJoin j;
if (cchPrev == 0) {
srcNew = srcAlloc;
offNew = offAlloc;
} else if (srcPrev == srcAlloc && offPrev + cchPrev == offAlloc) {
//noinspection ConstantConditions
assert srcPrev instanceof char[];
srcNew = srcPrev;
offNew = offPrev;
} else if (srcPrev instanceof CharJoin && (j = (CharJoin) srcPrev)._srcRight == srcAlloc &&
offPrev + cchPrev - j._cchLeft + j._offRight == offAlloc) {
assert j._srcRight instanceof char[];
srcNew = srcPrev;
offNew = offPrev;
} else {
j = new CharJoin(srcPrev, offPrev, cchPrev, srcAlloc, offAlloc);
offNew = 0;
srcNew = j._depth > CharJoin.MAX_DEPTH ? saveChars(j, 0, cchNew) : j;
}
// Now, srcNew and offNew specify the two parts of the triple which has the prev text and
// part of the text to save (if not all of it). Here I compute cchMore which is any
// remaining text which was not allocated for earlier. Effectively, this code deals with
// the case where the text to save was greater than the remaining space in the buffer and
// I need to allocate another buffer to save away the second part and then join the two.
int cchMore = cchSave - cchAlloc;
if (cchMore > 0) {
// If we're here the the buffer got consumed. So, this time it must allocate a new
// buffer capable of containing all of the remaining text (no matter how large) and
// return the beginning part of it.
srcAlloc = allocate(cchMore);
offAlloc = _offSrc;
cchAlloc = _cchSrc;
assert cchAlloc == cchMore;
assert offAlloc == 0;
getChars(srcAlloc, offAlloc, srcSave, offSave + (cchSave - cchMore), cchMore);
j = new CharJoin(srcNew, offNew, cchNew, srcAlloc, offAlloc);
offNew = 0;
cchNew += cchMore;
srcNew = j._depth > CharJoin.MAX_DEPTH ? saveChars(j, 0, cchNew) : j;
}
_offSrc = offNew;
_cchSrc = cchNew;
assert isValid(srcNew, _offSrc, _cchSrc);
return srcNew;
}
private static void dumpText(PrintStream o, String s) {
o.print("\"");
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if (i == 36) {
o.print("...");
break;
}
switch (ch) {
case '\n':
o.print("\\n");
break;
case '\r':
o.print("\\r");
break;
case '\t':
o.print("\\t");
break;
case '\f':
o.print("\\f");
break;
case '"':
o.print("\\\"");
break;
default:
o.print(ch);
break;
}
}
o.print("\"");
}
public static void dump(Object src, int off, int cch) {
dumpChars(System.out, src, off, cch);
System.out.println();
}
public static void dumpChars(PrintStream p, Object src, int off, int cch) {
p.print("off=" + off + ", cch=" + cch + ", ");
if (src == null) {
p.print("<null-src>");
} else if (src instanceof String) {
String s = (String) src;
p.print("String");
if (off != 0 || cch != s.length()) {
if (off < 0 || off > s.length() || off + cch < 0 || off + cch > s.length()) {
p.print(" (Error)");
return;
}
}
//p.print( ": " );
dumpText(p, s.substring(off, off + cch));
} else if (src instanceof char[]) {
char[] chars = (char[]) src;
p.print("char[]");
if (off != 0 || cch != chars.length) {
if (off < 0 || off > chars.length || off + cch < 0 || off + cch > chars.length) {
p.print(" (Error)");
return;
}
}
//p.print( ": " );
dumpText(p, new String(chars, off, cch));
} else if (src instanceof CharJoin) {
p.print("CharJoin");
((CharJoin) src).dumpChars(p, off, cch);
} else {
p.print("Unknown text source");
}
}
public static boolean isValid(Object src, int off, int cch) {
if (cch < 0 || off < 0) {
return false;
}
if (src == null) {
return off == 0 && cch == 0;
}
if (src instanceof char[]) {
char[] c = (char[]) src;
return off <= c.length && off + cch <= c.length;
}
if (src instanceof String) {
String s = (String) src;
return off <= s.length() && off + cch <= s.length();
}
if (src instanceof CharJoin) {
return ((CharJoin) src).isValid(off, cch);
}
return false;
}
//
// Private stuff
//
public static final class CharJoin {
public CharJoin(
Object srcLeft, int offLeft, int cchLeft, Object srcRight, int offRight) {
_srcLeft = srcLeft;
_offLeft = offLeft;
_cchLeft = cchLeft;
_srcRight = srcRight;
_offRight = offRight;
int depth = 0;
if (srcLeft instanceof CharJoin) {
depth = ((CharJoin) srcLeft)._depth;
}
if (srcRight instanceof CharJoin) {
int rightDepth = ((CharJoin) srcRight)._depth;
if (rightDepth > depth) {
depth = rightDepth;
}
}
_depth = depth + 1;
assert _depth <= MAX_DEPTH + 2;
}
private int cchRight(int off, int cch) {
return Math.max(0, cch - _cchLeft - off);
}
public int depth() {
int depth = 0;
if (_srcLeft instanceof CharJoin) {
depth = ((CharJoin) _srcLeft).depth();
}
if (_srcRight instanceof CharJoin) {
depth = Math.max(((CharJoin) _srcRight).depth(), depth);
}
return depth + 1;
}
public boolean isValid(int off, int cch) {
// Deep trees cause this to take forever
if (_depth > 2) {
return true;
}
assert _depth == depth();
if (off < 0 || cch < 0) {
return false;
}
if (!CharUtil.isValid(_srcLeft, _offLeft, _cchLeft)) {
return false;
}
return CharUtil.isValid(_srcRight, _offRight, cchRight(off, cch));
}
private void getString(StringBuffer sb, int off, int cch) {
assert cch > 0;
if (off < _cchLeft) {
int cchL = Math.min(_cchLeft - off, cch);
CharUtil.getString(sb, _srcLeft, _offLeft + off, cchL);
if (cch > cchL) {
CharUtil.getString(sb, _srcRight, _offRight, cch - cchL);
}
} else {
CharUtil.getString(sb, _srcRight, _offRight + off - _cchLeft, cch);
}
}
private void getChars(char[] chars, int start, int off, int cch) {
assert cch > 0;
if (off < _cchLeft) {
int cchL = Math.min(_cchLeft - off, cch);
CharUtil.getChars(chars, start, _srcLeft, _offLeft + off, cchL);
if (cch > cchL) {
CharUtil.getChars(chars, start + cchL, _srcRight, _offRight, cch - cchL);
}
} else {
CharUtil.getChars(chars, start, _srcRight, _offRight + off - _cchLeft, cch);
}
}
private void dumpChars(PrintStream p, int off, int cch) {
p.print("( ");
CharUtil.dumpChars(p, _srcLeft, _offLeft, _cchLeft);
p.print(", ");
CharUtil.dumpChars(p, _srcRight, _offRight, cchRight(off, cch));
p.print(" )");
}
//
//
//
public final Object _srcLeft;
public final int _offLeft;
public final int _cchLeft;
public final Object _srcRight;
public final int _offRight;
public final int _depth;
static final int MAX_DEPTH = 64;
}
//
//
//
public final static class CharIterator {
public void init(Object src, int off, int cch) {
init(src, off, cch, 0);
}
public void init(Object src, int off, int cch, int startPos) {
assert isValid(src, off, cch);
release();
_srcRoot = src;
_offRoot = off;
_cchRoot = cch;
_minPos = _maxPos = -1;
movePos(startPos);
}
public void release() {
_srcRoot = null;
_srcLeafString = null;
_srcLeafChars = null;
}
public boolean hasNext() {
return _pos < _cchRoot;
}
public boolean hasPrev() {
return _pos > 0;
}
public char next() {
assert hasNext();
char ch = currentChar();
movePos(_pos + 1);
return ch;
}
public char prev() {
assert hasPrev();
movePos(_pos - 1);
return currentChar();
}
public void movePos(int newPos) {
assert newPos >= 0 && newPos <= _cchRoot;
if (newPos < _minPos || newPos > _maxPos) {
// if newPos out of cached leaf, recache new leaf
Object src = _srcRoot;
int off = _offRoot + newPos;
int cch = _cchRoot;
for (_offLeaf = _offRoot; src instanceof CharJoin; ) {
CharJoin j = (CharJoin) src;
if (off < j._cchLeft) {
src = j._srcLeft;
_offLeaf = j._offLeft;
off = off + j._offLeft;
cch = j._cchLeft;
} else {
src = j._srcRight;
_offLeaf = j._offRight;
off = off - (j._cchLeft - j._offRight);
cch = cch - j._cchLeft;
}
}
// _offLeaf = off - Math.min( off - _offLeaf, newPos );
_minPos = newPos - (off - _offLeaf);
// _maxPos = newPos + Math.min( _cchRoot - newPos, sizeof( src ) - off );
_maxPos = _minPos + cch;
if (newPos < _cchRoot) {
_maxPos--;
}
// Cache the leaf src to avoid instanceof for every char
_srcLeafChars = null;
_srcLeafString = null;
if (src instanceof char[]) {
_srcLeafChars = (char[]) src;
} else {
_srcLeafString = (String) src;
}
assert newPos >= _minPos && newPos <= _maxPos;
}
_pos = newPos;
}
private char currentChar() {
int i = _offLeaf + _pos - _minPos;
return _srcLeafChars == null ? _srcLeafString.charAt(i) : _srcLeafChars[i];
}
private Object _srcRoot; // Original triple
private int _offRoot;
private int _cchRoot;
private int _pos; // Current position
private int _minPos; // Min/max poses for current cached leaf
private int _maxPos;
private int _offLeaf;
private String _srcLeafString; // Cached leaf - either a char[] or a string
private char[] _srcLeafChars;
}
private static final int CHARUTIL_INITIAL_BUFSIZE = 1024 * 32;
private static final ThreadLocal<SoftReference<CharUtil>> tl_charUtil =
ThreadLocal.withInitial(() -> new SoftReference<>(new CharUtil(CHARUTIL_INITIAL_BUFSIZE)));
public static void clearThreadLocals() {
tl_charUtil.remove();
}
private final CharIterator _charIter = new CharIterator();
// TODO - 64 is kinda arbitrary. Perhaps it should be configurable.
private static final int MAX_COPY = 64;
// Current char buffer we're allcoating new chars to
private final int _charBufSize;
private int _currentOffset;
private char[] _currentBuffer;
// These members are used to communicate offset and character count
// information back to a caller of various methods on CharUtil.
// Usually, the methods returns the src Object, and these two hold
// the offset and the char count.
public int _offSrc;
public int _cchSrc;
}