All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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