import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.Arrays;
/**
* Byte manipulation and display utilities
return newPos;
}
+ /**
+ * Utility byte stream similar to ByteArrayOutputStream but with extended
+ * accessibility to the bytes.
+ */
+ public static class ByteStream
+ {
+ private byte[] _bytes;
+ private int _length;
+ private int _lastLength;
+
+
+ public ByteStream() {
+ this(32);
+ }
+
+ public ByteStream(int capacity) {
+ _bytes = new byte[capacity];
+ }
+
+ public int getLength() {
+ return _length;
+ }
+
+ public byte[] getBytes() {
+ return _bytes;
+ }
+
+ protected void ensureNewCapacity(int numBytes) {
+ int newLength = _length + numBytes;
+ if(newLength > _bytes.length) {
+ byte[] temp = new byte[newLength * 2];
+ System.arraycopy(_bytes, 0, temp, 0, _length);
+ _bytes = temp;
+ }
+ }
+
+ public void write(int b) {
+ ensureNewCapacity(1);
+ _bytes[_length++] = (byte)b;
+ }
+
+ public void write(byte[] b) {
+ write(b, 0, b.length);
+ }
+
+ public void write(byte[] b, int offset, int length) {
+ ensureNewCapacity(length);
+ System.arraycopy(b, offset, _bytes, _length, length);
+ _length += length;
+ }
+
+ public byte get(int offset) {
+ return _bytes[offset];
+ }
+
+ public void set(int offset, byte b) {
+ _bytes[offset] = b;
+ }
+
+ public void writeFill(int length, byte b) {
+ ensureNewCapacity(length);
+ int oldLength = _length;
+ _length += length;
+ Arrays.fill(_bytes, oldLength, _length, b);
+ }
+
+ public void writeTo(ByteStream out) {
+ out.write(_bytes, 0, _length);
+ }
+
+ public byte[] toByteArray() {
+
+ byte[] result = null;
+ if(_length == _bytes.length) {
+ result = _bytes;
+ _bytes = null;
+ } else {
+ result = new byte[_length];
+ System.arraycopy(_bytes, 0, result, 0, _length);
+ if(_lastLength == _length) {
+ // if we get the same result length bytes twice in a row, clear the
+ // _bytes so that the next _bytes will be _lastLength
+ _bytes = null;
+ }
+ }
+
+ // save result length so we can potentially get the right length of the
+ // next byte[] in reset()
+ _lastLength = _length;
+
+ return result;
+ }
+
+ public void reset() {
+ _length = 0;
+ if(_bytes == null) {
+ _bytes = new byte[_lastLength];
+ }
+ }
+
+ public void trimTrailing(byte minTrimCode, byte maxTrimCode)
+ {
+ int minTrim = ByteUtil.asUnsignedByte(minTrimCode);
+ int maxTrim = ByteUtil.asUnsignedByte(maxTrimCode);
+
+ int idx = _length - 1;
+ while(idx >= 0) {
+ int val = asUnsignedByte(get(idx));
+ if((val >= minTrim) && (val <= maxTrim)) {
+ --idx;
+ } else {
+ break;
+ }
+ }
+
+ _length = idx + 1;
+ }
+ }
+
}
package com.healthmarketscience.jackcess;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.apache.commons.logging.LogFactory;
import static com.healthmarketscience.jackcess.IndexCodes.*;
+import static com.healthmarketscience.jackcess.ByteUtil.ByteStream;
/**
/** temp buffer used to read/write the index pages */
private final TempBufferHolder _indexBufferH =
TempBufferHolder.newHolder(TempBufferHolder.Type.SOFT, true);
+ /** temp buffer used to create index entries */
+ private ByteStream _entryBuffer;
/** max size for all the entries written to a given index data page */
private final int _maxPageEntrySize;
/** FIXME, for now, we can't write multi-page indexes or indexes using the funky primary key compression scheme */
return null;
}
- ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ if(_entryBuffer == null) {
+ _entryBuffer = new ByteStream();
+ }
+ _entryBuffer.reset();
for(ColumnDescriptor col : _columns) {
Object value = values[col.getColumnIndex()];
continue;
}
- col.writeValue(value, bout);
+ col.writeValue(value, _entryBuffer);
}
- return bout.toByteArray();
+ return _entryBuffer.toByteArray();
}
/**
* Flips all the bits in the byte array.
*/
private static byte[] flipBytes(byte[] value) {
- for(int i = 0; i < value.length; ++i) {
+ return flipBytes(value, 0, value.length);
+ }
+
+ /**
+ * Flips the bits in the specified bytes in the byte array.
+ */
+ private static byte[] flipBytes(byte[] value, int offset, int length) {
+ for(int i = offset; i < (offset + length); ++i) {
value[i] = (byte)(~value[i]);
}
return value;
* is based on a variety of nifty codes).
*/
private static void writeNonNullIndexTextValue(
- Object value, ByteArrayOutputStream bout, boolean isAscending)
+ Object value, ByteStream bout, boolean isAscending)
throws IOException
{
// first, convert to string
if(str.length() > MAX_TEXT_INDEX_CHAR_LENGTH) {
str = str.substring(0, MAX_TEXT_INDEX_CHAR_LENGTH);
}
-
- ByteArrayOutputStream tmpBout = bout;
- if(!isAscending) {
- // we need to accumulate the bytes in a temp array in order to negate
- // them before writing them to the final array
- tmpBout = new ByteArrayOutputStream();
- }
+
+ // record pprevious entry length so we can do any post-processing
+ // necessary for this entry (handling descending)
+ int prevLength = bout.getLength();
// now, convert each character to a "code" of one or more bytes
- ExtraCodesOutputStream extraCodes = null;
- ExtraCodesOutputStream unprintableCodes = null;
- ExtraCodesOutputStream crazyCodes = null;
+ ExtraCodesStream extraCodes = null;
+ ByteStream unprintableCodes = null;
+ ByteStream crazyCodes = null;
int charOffset = 0;
for(int i = 0; i < str.length(); ++i) {
byte[] bytes = ch.getInlineBytes();
if(bytes != null) {
// write the "inline" codes immediately
- tmpBout.write(bytes);
+ bout.write(bytes);
// only increment the charOffset for chars with inline codes
++charOffset;
byte extraCodeModifier = ch.getExtraByteModifier();
if((bytes != null) || (extraCodeModifier != 0)) {
if(extraCodes == null) {
- extraCodes = new ExtraCodesOutputStream(str.length());
+ extraCodes = new ExtraCodesStream(str.length());
}
// keep track of the extra codes for later
bytes = ch.getUnprintableBytes();
if(bytes != null) {
if(unprintableCodes == null) {
- unprintableCodes = new ExtraCodesOutputStream();
+ unprintableCodes = new ByteStream();
}
// keep track of the unprintable codes for later
byte crazyFlag = ch.getCrazyFlag();
if(crazyFlag != 0) {
if(crazyCodes == null) {
- crazyCodes = new ExtraCodesOutputStream();
+ crazyCodes = new ByteStream();
}
// keep track of the crazy flags for later
}
// write end text flag
- tmpBout.write(END_TEXT);
+ bout.write(END_TEXT);
boolean hasExtraCodes = trimExtraCodes(
extraCodes, (byte)0, INTERNATIONAL_EXTRA_PLACEHOLDER);
// we write all the international extra bytes first
if(hasExtraCodes) {
- extraCodes.writeTo(tmpBout);
+ extraCodes.writeTo(bout);
}
if(hasCrazyCodes || hasUnprintableCodes) {
// write 2 more end flags
- tmpBout.write(END_TEXT);
- tmpBout.write(END_TEXT);
+ bout.write(END_TEXT);
+ bout.write(END_TEXT);
// next come the crazy flags
if(hasCrazyCodes) {
- writeCrazyCodes(crazyCodes, tmpBout);
+ writeCrazyCodes(crazyCodes, bout);
}
// then we write all the unprintable extra bytes
if(hasUnprintableCodes) {
// write another end flag
- tmpBout.write(END_TEXT);
+ bout.write(END_TEXT);
- unprintableCodes.writeTo(tmpBout);
+ unprintableCodes.writeTo(bout);
}
}
}
// we actually write the end byte before flipping the bytes, and write
// another one after flipping
- tmpBout.write(END_EXTRA_TEXT);
+ bout.write(END_EXTRA_TEXT);
- // we actually wrote into a temporary array so that we can invert the
- // bytes before writing them to the final array
- bout.write(flipBytes(tmpBout.toByteArray()));
-
+ // flip the bytes that we have written thus far for this text value
+ flipBytes(bout.getBytes(), prevLength, (bout.getLength() - prevLength));
}
// write end extra text
private static void writeExtraCodes(
int charOffset, byte[] bytes, byte extraCodeModifier,
- ExtraCodesOutputStream extraCodes)
+ ExtraCodesStream extraCodes)
throws IOException
{
// we fill in a placeholder value for any chars w/out extra codes
int numChars = extraCodes.getNumChars();
if(numChars < charOffset) {
int fillChars = charOffset - numChars;
- extraCodes.write(fillChars, INTERNATIONAL_EXTRA_PLACEHOLDER);
+ extraCodes.writeFill(fillChars, INTERNATIONAL_EXTRA_PLACEHOLDER);
extraCodes.incrementNumChars(fillChars);
}
// the extra code modifier is added to the last extra code written. if
// there is no previous extra code, it is made the first extra code.
- int lastIdx = extraCodes.size() - 1;
+ int lastIdx = extraCodes.getLength() - 1;
if(lastIdx >= 0) {
byte lastByte = extraCodes.get(lastIdx);
lastByte += extraCodeModifier;
}
}
- private static boolean trimExtraCodes(ExtraCodesOutputStream extraCodes,
+ private static boolean trimExtraCodes(ByteStream extraCodes,
byte minTrimCode, byte maxTrimCode)
throws IOException
{
return false;
}
- extraCodes.trimTrailingBytes(minTrimCode, maxTrimCode);
+ extraCodes.trimTrailing(minTrimCode, maxTrimCode);
// anything left?
- return (extraCodes.size() > 0);
+ return (extraCodes.getLength() > 0);
}
private static void writeUnprintableCodes(
- int charOffset, byte[] bytes, ExtraCodesOutputStream extraCodes)
+ int charOffset, byte[] bytes, ByteStream extraCodes)
throws IOException
{
// we write a whacky combo of bytes for each unprintable char which
extraCodes.write(bytes);
}
- private static void writeCrazyCodes(ExtraCodesOutputStream crazyCodes,
- ByteArrayOutputStream tmpBout)
+ private static void writeCrazyCodes(ByteStream crazyCodes, ByteStream bout)
throws IOException
{
// CRAZY_CODE_2 flags at the end are ignored, so ditch them
trimExtraCodes(crazyCodes, CRAZY_CODE_2, CRAZY_CODE_2);
- if(crazyCodes.size() > 0) {
+ if(crazyCodes.getLength() > 0) {
// the crazy codes get encoded into 6 bit sequences where each code is 2
// bits (where the first 2 bits in the byte are a common prefix).
byte curByte = CRAZY_CODE_START;
int idx = 0;
- for(int i = 0; i < crazyCodes.size(); ++i) {
+ for(int i = 0; i < crazyCodes.getLength(); ++i) {
byte nextByte = crazyCodes.get(i);
nextByte <<= ((2 - idx) * 2);
curByte |= nextByte;
++idx;
if(idx == 3) {
// write current byte and reset
- tmpBout.write(curByte);
+ bout.write(curByte);
curByte = CRAZY_CODE_START;
idx = 0;
}
// write last byte
if(idx > 0) {
- tmpBout.write(curByte);
+ bout.write(curByte);
}
}
// write crazy code suffix (note, we write this even if all the codes are
// trmmed
- tmpBout.write(CRAZY_CODES_SUFFIX);
+ bout.write(CRAZY_CODES_SUFFIX);
}
/**
return (value == null);
}
- protected final void writeValue(Object value, ByteArrayOutputStream bout)
+ protected final void writeValue(Object value, ByteStream bout)
throws IOException
{
if(isNullValue(value)) {
}
protected abstract void writeNonNullValue(
- Object value, ByteArrayOutputStream bout)
+ Object value, ByteStream bout)
throws IOException;
@Override
@Override
protected void writeNonNullValue(
- Object value, ByteArrayOutputStream bout)
+ Object value, ByteStream bout)
throws IOException
{
byte[] valueBytes = encodeNumberColumnValue(value, getColumn());
@Override
protected void writeNonNullValue(
- Object value, ByteArrayOutputStream bout)
+ Object value, ByteStream bout)
throws IOException
{
byte[] valueBytes = encodeNumberColumnValue(value, getColumn());
@Override
protected void writeNonNullValue(
- Object value, ByteArrayOutputStream bout)
+ Object value, ByteStream bout)
throws IOException
{
byte[] valueBytes = encodeNumberColumnValue(value, getColumn());
@Override
protected void writeNonNullValue(
- Object value, ByteArrayOutputStream bout)
+ Object value, ByteStream bout)
throws IOException
{
byte[] valueBytes = encodeNumberColumnValue(value, getColumn());
}
@Override
- protected void writeNonNullValue(Object value, ByteArrayOutputStream bout)
+ protected void writeNonNullValue(Object value, ByteStream bout)
throws IOException
{
bout.write(
@Override
protected void writeNonNullValue(
- Object value, ByteArrayOutputStream bout)
+ Object value, ByteStream bout)
throws IOException
{
writeNonNullIndexTextValue(value, bout, isAscending());
@Override
protected void writeNonNullValue(
- Object value, ByteArrayOutputStream bout)
+ Object value, ByteStream bout)
throws IOException
{
byte[] valueBytes = encodeNumberColumnValue(value, getColumn());
}
@Override
- protected void writeNonNullValue(Object value, ByteArrayOutputStream bout)
+ protected void writeNonNullValue(Object value, ByteStream bout)
throws IOException
{
throw new UnsupportedOperationException("should not be called");
/**
- * Extension of ByteArrayOutputStream which allows more complex access to
- * the underlying bytes, as well as keeps track of an additional char count.
+ * Extension of ByteStream which keeps track of an additional char count.
*/
- private static final class ExtraCodesOutputStream
- extends ByteArrayOutputStream
+ private static final class ExtraCodesStream extends ByteStream
{
private int numChars;
- private ExtraCodesOutputStream() {
- super();
- }
-
- private ExtraCodesOutputStream(int length) {
+ private ExtraCodesStream(int length) {
super(length);
}
return numChars;
}
- public byte get(int offset) {
- return buf[offset];
- }
-
- public void set(int offset, byte b) {
- buf[offset] = b;
- }
-
- public void write(int numBytes, byte b) {
- for(int i = 0; i < numBytes; ++i) {
- write(b);
- }
- }
-
public void incrementNumChars(int inc) {
numChars += inc;
}
-
- public void trimTrailingBytes(byte minTrimCode, byte maxTrimCode)
- {
- int minTrim = ByteUtil.asUnsignedByte(minTrimCode);
- int maxTrim = ByteUtil.asUnsignedByte(maxTrimCode);
-
- int idx = count - 1;
- while(idx >= 0) {
- int val = ByteUtil.asUnsignedByte(get(idx));
- if((val >= minTrim) && (val <= maxTrim)) {
- --idx;
- } else {
- break;
- }
- }
-
- count = idx + 1;
- }
}
}