
org.ttzero.excel.entity.e3.Block Maven / Gradle / Ivy
/*
* Copyright (c) 2019-2020, [email protected] All Rights Reserved.
*
* 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.ttzero.excel.entity.e3;
import org.ttzero.excel.reader.ExcelReadException;
import org.ttzero.excel.util.StringUtil;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import static org.ttzero.excel.entity.e3.StandardTypeByteSize.BYTE;
import static org.ttzero.excel.entity.e3.StandardTypeByteSize.SHORT;
import static org.ttzero.excel.entity.e3.StandardTypeByteSize.INT;
import static org.ttzero.excel.entity.e3.StandardTypeByteSize.LONG;
/**
* Sector block
*
* @author guanquan.wang at 2019-01-29 11:19
*/
public class Block implements Serializable {
private static final long serialVersionUID = 1L;
/**
* Sector data buffer
*/
protected ByteBuffer buffer;
/**
* Data size from offset
* size = bytes.length - offset + 1
*/
private int size;
/**
* Use for reset
*/
int offset;
/**
* Document context
*/
protected Context context;
/**
* Current sector id
*/
protected int sid;
/**
* The Header sector id
* if block has mutable sat, hsid is the block header sector id
*/
private int hsid;
/**
* Remaining buffer
*/
protected byte[] remainingBuffer;
/**
* Size of remaining buffer
*/
protected int remainingSize;
/**
* The block length
*/
private int block_size;
/**
* The last sector index
*/
private int eof;
/**
* The transaction for each record
*/
LinkedList transaction;
/**
* The total size in bytes of the next packet.
*/
int packetSize;
/**
* Cache the pre record id
*/
private short preRecord;
private boolean usePre;
/**
* Load block by sid
*
* @param context the workbook {@link Context}
* @param sid the sector id
*/
public Block(Context context, int sid) {
this.context = context;
this.sid = sid;
this.context.sectorTable.moveTo(sid);
this.block_size = 1 << (context.split + context.ssz);
}
public Block init() {
buffer = ByteBuffer.allocate(block_size);
buffer.order(context.byteOrder.getByteOrder());
remainingBuffer = new byte[10];
transaction = new LinkedList<>();
load();
return this;
}
/**
* Load sector from disk
*/
protected void load() {
// Load sector
context.read(sid, buffer);
int n = sid >> context.split;
offset = (sid - (n << context.split)) << context.ssz;
buffer.position(offset);
eof = offset + context.sectorSize;
hsid = n << context.split;
}
/**
* Check sector range and load next sector when read length big than size
*
* @param length the value length
* @return true if bits all in range
* @throws EndOfChainException if read a EOC sid
*/
protected boolean rangeCheck(int length) {
Mark mark = transaction.peek();
if (mark != null) {
mark.product(length);
}
int lastSize = lastSize();
if (lastSize >= length) {
remainingSize &= 0;
return true;
}
// Reset remaining size
remainingSize = lastSize;
int next_sid = context.sectorTable.nextSecID();
// End of Chain SID
if (next_sid == SectorTable.EOC) {
throw new EndOfChainException("End Of Chain SecID. sid: " + sid + ", next_sid: " + next_sid);
}
// TODO length large than the block size
// do {
// this.remainingSize = buffer.remaining();
// load();
// } while (length > buffer.remaining());
testShouldLoad(next_sid);
return remainingSize == 0;
}
/**
* Test the next sector is should load.
*
* @param next_sid the next sector id
*/
protected void testShouldLoad(int next_sid) {
if (sid == next_sid) {
buffer.position(offset);
afterReset();
return;
}
int p = sid;
context.sectorTable.moveTo(sid = next_sid);
// An adjacent blocks
if (next_sid - p == 1 && buffer.remaining() >= context.sectorSize) {
offset = eof;
remainingSize &= 0;
eof += context.sectorSize;
afterReset();
return;
}
remaining();
int c = next_sid - hsid;
// Reset offset when the next sid in current block
if (c >= 0 && c < size >> context.ssz) {
offset = c << context.ssz;
buffer.position(offset);
eof = offset + context.sectorSize;
afterReset();
// Load the next sector
} else if (next_sid >= 0) {
load();
}
}
/**
* Reset header sector id
*/
protected void afterReset() { }
/**
* Identifier
*
* @return the next identifier
*/
public short nextIdentifier() {
if (usePre) {
usePre = false;
return preRecord;
}
return preRecord = nextShort();
}
/**
* Use pre identifier
*/
public void cacheIdentifier() {
usePre = true;
}
/**
* Read byte
*
* @return the byte value
*/
public byte nextByte() {
rangeCheck(BYTE);
return buffer.get();
}
/**
* Read short
*
* @return the short value
*/
public short nextShort() {
return (short) nextUnsignedShort();
}
/**
* Read unsigned short
*
* @return an unsigned short value
*/
public int nextUnsignedShort() {
return rangeCheck(SHORT)
? buffer.get() & 0xFF | (buffer.get() & 0xFF) << 8
: remainingBuffer[0] & 0xFF | (buffer.get() & 0xFF) << 8;
}
/**
* Read char
*
* @return the char value
*/
public char nextChar() {
return (char) nextShort();
}
/**
* Read compression char
*
* @return the compression char value
*/
public char nextComprChar() {
return (char) nextByte();
}
/**
* Read int
*
* @return the int value
*/
public int nextInt() {
if (!rangeCheck(INT)) {
int value = 0, i = 0;
for (; i < remainingSize; ) {
value |= (remainingBuffer[i] & 0xFF) << (i++ << 3);
}
while (INT - i > 0) {
value |= (buffer.get() & 0xFF) << (i++ << 3);
}
return value;
}
return buffer.getInt();
}
/**
* Read long
*
* @return the long value
*/
public long nextLong() {
return nextLong(LONG);
}
/**
* Read long
*
* @param length bit length
* @return the long value
*/
public long nextLong(int length) {
long value = 0L;
int i = 0;
if (!rangeCheck(length)) {
for (; i < remainingSize; ) {
value |= ((remainingBuffer[i] & 0xFF) & -1L) << (i++ << 3);
}
}
while (length - i > 0) {
value |= ((buffer.get() & 0xFF) & -1L) << (i++ << 3);
}
return value;
}
/**
* Read single-precision floating-point
*
* @return the float value
*/
public float nextFloat() {
return Float.intBitsToFloat(nextInt());
}
/**
* Read double-precision floating-point
*
* @return the double value
*/
public double nextDouble() {
return Double.longBitsToDouble(nextLong());
}
/**
* Read double-precision floating-point
*
* @param size bit size
* @return the double value
*/
public double nextDouble(int size) {
if (size > LONG) {
throw new ExcelReadException("Overly large size, max " + LONG + ", current " + size);
}
long n = nextLong(size);
if (size <= 4) {
n <<= 32;
}
return Double.longBitsToDouble(n);
}
/**
* Read A GUID that identifies a software component
*
* @return uuid
*/
public UUID nextGUID() {
long mostSigBits = nextLong(INT);
mostSigBits <<= 16;
mostSigBits |= nextLong(SHORT);
mostSigBits <<= 16;
mostSigBits |= nextLong(SHORT);
// big-endian order
long leastSigBits = 0L;
byte[] range = range(LONG);
for (int i = LONG - 1, _i = 0; i >= 0; i--) {
leastSigBits |= ((range[i] & 0xFF) & -1L) << (_i++ << 3);
}
return new UUID(mostSigBits, leastSigBits);
}
/**
* 16-bit only don't content Asian phonetic and Rich-Text
*
* @param size character count
* @param option option flag. if bit 0 equal zero encode by 8-bytes
* if bit 0 equal one encode by 16-bytes
* @return ASCII or UTF16-LT string
*/
public String utf(int size, Option option) {
// Character compression (ccompr)
Charset charset;
// Uncompressed (16-bit characters)
if (option.isOn(0)) {
size <<= 1;
charset = StandardCharsets.UTF_16LE;
} else {
charset = StandardCharsets.US_ASCII;
}
// Block has CONTINUE record
Mark mark = transaction.peek();
if (mark != null && mark.product + size > packetSize) {
int last = packetSize - mark.product;
String s1;
if (last > 0) {
byte[] bytes1 = range(last);
s1 = new String(bytes1, 0, last, charset);
} else s1 = StringUtil.EMPTY;
// mark a CONTINUE record
mark.CONTINUE();
short id = nextIdentifier();
if (id != ParserIdentifier.CONTINUE) {
throw new ExcelReadException("There has a error block.");
}
// Record data size
packetSize = nextShort();
mark.product = 0;
int m_size = size - last;
if (m_size > 0) {
if (option.isOn(0)) m_size >>= 1;
option = Option.of(nextByte());
if (option.isOn(0)) {
m_size <<= 1;
charset = StandardCharsets.UTF_16LE;
} else {
charset = StandardCharsets.US_ASCII;
}
byte[] bytes2 = range(m_size);
String s2 = new String(bytes2, 0, m_size, charset);
return s1 + s2;
} else return s1;
}
String str;
// Copy to a new byte array
rangeCheck(size);
if (remainingBuffer.length >= size) {
buffer.get(remainingBuffer, remainingSize, size - remainingSize);
str = new String(remainingBuffer, 0, size, charset);
} else if (size <= (1 << context.sssz)) {
remainingBuffer = Arrays.copyOf(remainingBuffer, 1 << context.sssz);
buffer.get(remainingBuffer, remainingSize, size - remainingSize);
str = new String(remainingBuffer, 0, size, charset);
} else {
byte[] data = Arrays.copyOf(remainingBuffer, size);
buffer.get(data, remainingSize, size - remainingSize);
str = new String(data, 0, size, charset);
}
// If end of block
if (mark != null && mark.product == packetSize) {
short id = nextIdentifier();
if (id == ParserIdentifier.CONTINUE) {
// mark a CONTINUE record
mark.CONTINUE();
// Record data size
packetSize = nextShort();
mark.product = 0;
} else cacheIdentifier();
}
return str;
}
// FIXME Wrong implementation
public String nextString(int size, Charset charset) {
byte[] data = range(size);
// including the null terminator
// int i = size - 1;
// for (; i >= 0; i--) {
// if (data[i] != 0) break;
// }
// if ((charset.equals(StandardCharsets.UTF_16LE)
// || charset.equals(StandardCharsets.UTF_16)
// || charset.equals(StandardCharsets.UTF_16BE)) && (i & 1) == 0)
// {
// i++;
// }
int n = trim(data, charset);
return charset.decode(ByteBuffer.wrap(data, 0, n)).toString();
// return new String(data, 0, i + 1, charset);
}
private int trim(byte[] bytes, Charset charset) {
int n = bytes.length;
if (charset.equals(StandardCharsets.UTF_16LE)) {
for (int i = 0; i < bytes.length - 1; i += 2) {
if (bytes[i] == 0x0 && bytes[i + 1] == 0x0) {
n = i;
break;
}
}
} else {
for (int i = 0; i < bytes.length; i++) {
if (bytes[i] == 0x0) {
n = i;
break;
}
}
}
return n;
}
/**
* Last bit size
*
* @return the last bit size
*/
public int lastSize() {
return eof - buffer.position();
}
/**
* Reset offset
*/
public void reset() {
Mark mark = transaction.peek();
if (mark != null) {
if (sid != mark.sid) {
sid = mark.sid;
load();
}
buffer.position(mark.offset);
mark.product = 0;
} else {
buffer.position(offset);
}
}
/**
* skip some bit-value
*
* @param size skip bit size
*/
public void skip(int size) {
Mark mark = transaction.peek();
if (mark != null) {
mark.product(size);
}
// Last size
int lastSize = lastSize();
// Turn back
if (size < 0) {
// Retreat across Sector
if (context.sectorSize - lastSize > -size && context.sectorTable.preSecID() != sid - 1) {
throw new IllegalArgumentException("Skip a negative sector");
}
buffer.position(buffer.position() + size);
return;
}
if (lastSize > size) {
buffer.position(buffer.position() + size);
return;
}
// Skip Multiple sat
buffer.position(buffer.position() + lastSize);
size -= lastSize;
context.sectorTable.moveTo(sid);
int next_sid = context.sectorTable.nextSecID(), sat = 1 << context.ssz;
while (next_sid != SectorTable.EOC && size > sat) {
next_sid = context.sectorTable.nextSecID();
size -= sat;
}
// End of Chain SID
if (next_sid == SectorTable.EOC && size > 0) {
throw new EndOfChainException("Skip size out of bound.");
}
testShouldLoad(next_sid);
buffer.position(buffer.position() + size);
}
/**
* bit array range
*
* @param length range length
* @return bit range
*/
public byte[] range(int length) {
if (remainingBuffer.length < length) {
remainingBuffer = new byte[length];
}
rangeCheck(length);
buffer.get(remainingBuffer, remainingSize, length - remainingSize);
return remainingBuffer;
}
/**
* @return current sector id
*/
public int getSid() {
return this.sid;
}
/**
* Marks the block is ready to read.
*
* @return the total size in bytes of the next packet.
*/
public int ready() {
// Size of the following data
this.packetSize = nextShort();
transaction.push(Mark.of(sid, buffer.position()));
return this.packetSize;
}
/**
* Read the packet finish.
*/
public void commit() {
Mark mark = transaction.peek();
if (mark == null) return;
int endOffset = mark.product;
// warn message
// if (endOffset > packetSize) {
// LOGGER.warn("Read packet byte size out of Packet-Size. " +
// "packet-size: " + packetSize + ", read-size: " + endOffset);
// }
if (packetSize > endOffset) {
skip(packetSize - endOffset);
}
transaction.pop();
Mark _mark;
if ((_mark = transaction.peek()) != null) {
_mark.product(mark.product);
}
}
/**
* @return the position from ready mark.
*/
public int position() {
Mark mark = transaction.peek();
if (mark != null) {
if (mark.sid != sid) {
int n = 0;
context.sectorTable.moveTo(mark.sid);
for (int fst = mark.sid; fst != sid; fst = context.sectorTable.nextSecID(), n++) ;
// end of block
if (lastSize() == 0) n++;
return (n - 1 << context.ssz) + context.sectorSize - mark.offset + buffer.position();
} else return buffer.position() - mark.offset;
}
return -1;
}
/**
* @return the context
*/
public Context getContext() {
return context;
}
public ShortBlock asShortBlock(int ssid) {
return new ShortBlock(context, ssid, -1);
}
public Block deepClone() {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
oos.flush();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
return (Block) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
Block cp = new Block(context, sid);
cp.offset = offset;
LinkedList _trans = new LinkedList<>();
for (Mark mark : transaction) {
Mark _mark = new Mark(mark.sid, mark.ssid, mark.hssid, mark.offset);
_mark.product = mark.product;
_trans.push(_mark);
}
cp.transaction = _trans;
cp.size = size;
cp.hsid = hsid;
cp.packetSize = packetSize;
cp.eof = eof;
cp.block_size = block_size;
cp.usePre = usePre;
cp.preRecord = preRecord;
cp.buffer = ByteBuffer.allocate(block_size);
cp.buffer.order(buffer.order());
int pos = buffer.position();
buffer.position(0);
cp.buffer.put(buffer);
cp.buffer.flip();
cp.buffer.position(pos);
buffer.position(pos);
cp.remainingBuffer = Arrays.copyOf(remainingBuffer, remainingBuffer.length);
cp.remainingSize = remainingSize;
return cp;
}
}
// --- Static
public static float IEEE754SinglePrecision(int i) {
return Float.intBitsToFloat(i);
}
public static double IEEE754DoublePrecision(long i) {
return Double.longBitsToDouble(i);
}
/**
* Warp remaining data
*/
protected void remaining() {
if (remainingSize <= 0) return;
// Resize if remaining over flow
if (remainingBuffer.length < remainingSize) {
remainingBuffer = new byte[remainingSize];
}
buffer.get(remainingBuffer, 0, remainingSize);
}
// --- FOR TEST
Block(Context context, byte[] bytes) {
this.context = context;
this.buffer = ByteBuffer.wrap(bytes);
this.buffer.order(context.byteOrder.getByteOrder());
this.remainingBuffer = new byte[10];
this.size = this.eof = (short) bytes.length;
this.sid = this.hsid = this.offset = 0;
this.packetSize = size;
transaction = new LinkedList<>();
}
static class Mark {
int sid;
int ssid;
int hssid;
int offset;
int product;
List continues;
private Mark(int sid, int offset) {
this(sid, -1, -1, offset);
}
private Mark(int sid, int ssid, int hssid, int offset) {
this.sid = sid;
this.ssid = ssid;
this.hssid = hssid;
this.offset = offset;
}
static Mark of(int sid, int offset) {
return new Mark(sid, offset);
}
static Mark of(int sid, int ssid, int hssid, int offset) {
return new Mark(sid, ssid, hssid, offset);
}
void product(int n) {
product += n;
}
void CONTINUE() {
if (continues == null) {
continues = new LinkedList<>();
}
continues.add(new Continue(sid, offset, product));
}
}
/**
* Whenever the content of a record exceeds the given limits (see table),
* the record must be split. Several CONTINUE records containing the
* additional data are added after the parent record.
*
* BIFF version | Maximum data size of a record
* -------------|-------------------------------
* BIFF2-BIFF5 | 2080 bytes (2084 bytes including record header)
* BIFF8 | 8224 bytes (8228 bytes including record header)
*
*/
private static class Continue {
int sid;
int offset;
int product;
Continue(int sid, int offset, int product) {
this.sid = sid;
this.offset = offset;
this.product = product;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy