Package org.msgpack.core.buffer

Source Code of org.msgpack.core.buffer.MessageBuffer

package org.msgpack.core.buffer;

import org.msgpack.core.annotations.Insecure;
import sun.misc.Unsafe;
import sun.nio.ch.DirectBuffer;

//import java.lang.invoke.MethodHandle;
//import java.lang.invoke.MethodHandles;
//import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import static org.msgpack.core.Preconditions.*;

/**
* MessageBuffer class is an abstraction of memory for reading/writing message packed data.
* This MessageBuffers ensures short/int/float/long/double values are written in the big-endian order.
*
* This class is optimized for fast memory access, so many methods are
* implemented without using any interface method that produces invokeinterface call in JVM.
* Compared to invokevirtual, invokeinterface is 30% slower in general because it needs to find a target function from the table.
*
*/
public class MessageBuffer {

    static final Unsafe unsafe;
    // TODO We should use MethodHandle for efficiency, but it is not available in JDK6
    static final Constructor byteBufferConstructor;
    static final boolean isByteBufferConstructorTakesBufferReference;
    static final int ARRAY_BYTE_BASE_OFFSET;
    static final int ARRAY_BYTE_INDEX_SCALE;

    static {
        try {
            // Fetch theUnsafe object for Orackle JDK and OpenJDK
            Unsafe u;
            try {
                Field field = Unsafe.class.getDeclaredField("theUnsafe");
                field.setAccessible(true);
                u = (Unsafe) field.get(null);
            }
            catch(NoSuchFieldException e) {
                // Workaround for creating an Unsafe instance for Android OS
                Constructor<Unsafe> unsafeConstructor = Unsafe.class.getDeclaredConstructor();
                unsafeConstructor.setAccessible(true);
                u = (Unsafe) unsafeConstructor.newInstance();
            }
            unsafe = u;
            if (unsafe == null) {
                throw new RuntimeException("Unsafe is unavailable");
            }

            ARRAY_BYTE_BASE_OFFSET = unsafe.arrayBaseOffset(byte[].class);
            ARRAY_BYTE_INDEX_SCALE = unsafe.arrayIndexScale(byte[].class);

            // Make sure the VM thinks bytes are only one byte wide
            if (ARRAY_BYTE_INDEX_SCALE != 1) {
                throw new IllegalStateException("Byte array index scale must be 1, but is " + ARRAY_BYTE_INDEX_SCALE);
            }

            // Find the hidden constructor for DirectByteBuffer
            Class<?> directByteBufferClass = ClassLoader.getSystemClassLoader().loadClass("java.nio.DirectByteBuffer");
            Constructor directByteBufferConstructor = null;
            boolean isAcceptReference = true;
            try {
                // TODO We should use MethodHandle for Java7, which can avoid the cost of boxing with JIT optimization
                directByteBufferConstructor = directByteBufferClass.getDeclaredConstructor(long.class, int.class, Object.class);
            }
            catch(NoSuchMethodException e) {
                directByteBufferConstructor = directByteBufferClass.getDeclaredConstructor(long.class, int.class);
                isAcceptReference = true;
            }
            byteBufferConstructor = directByteBufferConstructor;
            isByteBufferConstructorTakesBufferReference = isAcceptReference;
            if(byteBufferConstructor == null)
                throw new RuntimeException("Constructor of DirectByteBuffer is not found");
            byteBufferConstructor.setAccessible(true);

            // Check the endian of this CPU
            boolean isLittleEndian = true;
            byte[] a = new byte[8];
            unsafe.putLong(a, (long) ARRAY_BYTE_BASE_OFFSET, 0x0102030405060708L);
            switch (a[0]) {
                case 0x01:
                    isLittleEndian = false;
                    break;
                case 0x08:
                    isLittleEndian = true;
                    break;
                default:
                    assert false;
            }

            String bufferClsName = isLittleEndian ? "org.msgpack.core.buffer.MessageBuffer" : "org.msgpack.core.buffer.MessageBufferBE";
            Class<?> bufferCls = Class.forName(bufferClsName);
            msgBufferClass = bufferCls;

            Constructor<?> mbArrCstr = bufferCls.getDeclaredConstructor(byte[].class);
            mbArrCstr.setAccessible(true);
            mbArrConstructor = mbArrCstr;

            Constructor<?> mbBBCstr = bufferCls.getDeclaredConstructor(ByteBuffer.class);
            mbBBCstr.setAccessible(true);
            mbBBConstructor = mbBBCstr;

            // Requires Java7
            //newMsgBuffer = MethodHandles.lookup().unreflectConstructor(mbArrCstr).asType(
            //    MethodType.methodType(bufferCls, byte[].class)
            //);
        }
        catch (Exception e) {
            e.printStackTrace(System.err);
            throw new RuntimeException(e);
        }
    }


    /**
     * MessageBuffer class to use. If this machine is big-endian, it uses MessageBufferBE, which overrides some methods in this class that translate endians. If not, uses MessageBuffer.
     */
    private final static Class<?> msgBufferClass;

    private final static Constructor<?> mbArrConstructor;
    private final static Constructor<?> mbBBConstructor;


    // Requires Java7
    //private final static MethodHandle newMsgBuffer;

    /**
     * Base object for resolving the relative address of the raw byte array.
     * If base == null, the address value is a raw memory address
     */
    protected final Object base;

    /**
     * Head address of the underlying memory. If base is null, the address is a direct memory address, and if not,
     * it is the relative address within an array object (base)
     */
    protected final long address;

    /**
     * Size of the underlying memory
     */
    protected final int size;

    /**
     * Reference is used to hold a reference to an object that holds the underlying memory so that it cannot be
     * released by the garbage collector.
     */
    protected final ByteBuffer reference;

    // TODO life-time managment of this buffer
    //private AtomicInteger referenceCounter;


    static MessageBuffer newOffHeapBuffer(int length) {
        long address = unsafe.allocateMemory(length);
        return new MessageBuffer(address, length);
    }

    public static MessageBuffer newDirectBuffer(int length) {
        ByteBuffer m = ByteBuffer.allocateDirect(length);
        return newMessageBuffer(m);
    }

    public static MessageBuffer newBuffer(int length) {
        return newMessageBuffer(new byte[length]);
    }

    public static MessageBuffer wrap(byte[] array) {
        return newMessageBuffer(array);
    }

    public static MessageBuffer wrap(ByteBuffer bb) {
        return newMessageBuffer(bb);
    }

    /**
     * Creates a new MessageBuffer instance backed by ByteBuffer
     * @param bb
     * @return
     */
    private static MessageBuffer newMessageBuffer(ByteBuffer bb) {
       checkNotNull(bb);
       try {
           // We need to use reflection to create MessageBuffer instances in order to prevent TypeProfile generation for getInt method. TypeProfile will be
           // generated to resolve one of the method references when two or more classes overrides the method.
           return (MessageBuffer) mbBBConstructor.newInstance(bb);
       }
       catch(Exception e) {
           throw new RuntimeException(e);
       }
    }

    /**
     * Creates a new MessageBuffer instance backed by a java heap array
     * @param arr
     * @return
     */
    private static MessageBuffer newMessageBuffer(byte[] arr) {
        checkNotNull(arr);
        try {
            return (MessageBuffer) mbArrConstructor.newInstance(arr);
        }
        catch(Throwable e) {
            throw new RuntimeException(e);
        }
    }


    public static void releaseBuffer(MessageBuffer buffer) {
        if(buffer.base instanceof byte[]) {
            // We have nothing to do. Wait until the garbage-collector collects this array object
        }
        else if(buffer.base instanceof DirectBuffer) {
            ((DirectBuffer) buffer.base).cleaner().clean();
        }
        else {
            // Maybe cannot reach here
            unsafe.freeMemory(buffer.address);
        }
    }


    /**
     * Create a MessageBuffer instance from a given memory address and length
     * @param address
     * @param length
     */
    MessageBuffer(long address, int length) {
        this.base = null;
        this.address = address;
        this.size = length;
        this.reference = null;
    }

    /**
     * Create a MessageBuffer instance from a given ByteBuffer instance
     * @param bb
     */
    MessageBuffer(ByteBuffer bb) {
        if(bb.isDirect()) {
            // Direct buffer or off-heap memory
            DirectBuffer db = DirectBuffer.class.cast(bb);
            this.base = null;
            this.address = db.address();
            this.size = bb.capacity();
            this.reference = bb;
        }
        else if(bb.hasArray()) {
            this.base = bb.array();
            this.address = ARRAY_BYTE_BASE_OFFSET;
            this.size = bb.array().length;
            this.reference = null;
        } else {
            throw new IllegalArgumentException("Only the array-backed ByteBuffer or DirectBuffer are supported");
        }
    }


    /**
     * Create a MessageBuffer instance from an java heap array
     * @param arr
     */
    MessageBuffer(byte[] arr) {
        this.base = arr;
        this.address = ARRAY_BYTE_BASE_OFFSET;
        this.size = arr.length;
        this.reference = null;
    }

    MessageBuffer(Object base, long address, int length, ByteBuffer reference) {
        this.base = base;
        this.address = address;
        this.size = length;
        this.reference = reference;
    }

    /**
     * byte size of the buffer
     * @return
     */
    public int size() { return size; }


    public MessageBuffer slice(int offset, int length) {
        // TODO ensure deleting this slice does not collapse this MessageBuffer
        if(offset == 0 && length == size())
            return this;
        else {
            checkArgument(offset + length <= size());
            return new MessageBuffer(base, address + offset, length, reference);
        }
    }

    public byte getByte(int index) {
        return unsafe.getByte(base, address + index);
    }

    public boolean getBoolean(int index) {
        return unsafe.getBoolean(base, address + index);
    }

    public short getShort(int index) {
        short v = unsafe.getShort(base, address + index);
        return Short.reverseBytes(v);
    }

    /**
     * Read a big-endian int value at the specified index
     * @param index
     * @return
     */
    public int getInt(int index) {
        // Reading little-endian value
        int i = unsafe.getInt(base, address + index);
        // Reversing the endian
        return Integer.reverseBytes(i);
    }

    public float getFloat(int index) {
        return Float.intBitsToFloat(getInt(index));
    }

    public long getLong(int index) {
        long l = unsafe.getLong(base, address + index);
        return Long.reverseBytes(l);
    }

    public double getDouble(int index) {
        return Double.longBitsToDouble(getLong(index));
    }

    public void getBytes(int index, byte[] dst, int dstOffset, int length) {
        unsafe.copyMemory(base, address+index, dst, ARRAY_BYTE_BASE_OFFSET + dstOffset, length);
    }

    public void getBytes(int index, int len, ByteBuffer dst) {
        if(dst.remaining() > len)
            throw new BufferOverflowException();
        ByteBuffer src = toByteBuffer(index, len);
        dst.put(src);
    }


    public void putByte(int index, byte v) {
        unsafe.putByte(base, address + index, v);
    }

    public void putBoolean(int index, boolean v) {
        unsafe.putBoolean(base, address + index, v);
    }

    public void putShort(int index, short v) {
        v = Short.reverseBytes(v);
        unsafe.putShort(base, address + index, v);
    }

    /**
     * Write a big-endian integer value to the memory
     * @param index
     * @param v
     */
    public void putInt(int index, int v){
        // Reversing the endian
        v = Integer.reverseBytes(v);
        unsafe.putInt(base, address + index, v);
    }

    public void putFloat(int index, float v) {
        putInt(index, Float.floatToRawIntBits(v));
    }

    public void putLong(int index, long l) {
        // Reversing the endian
        l = Long.reverseBytes(l);
        unsafe.putLong(base, address + index, l);
    }

    public void putDouble(int index, double v) {
        putLong(index, Double.doubleToRawLongBits(v));
    }

    public void putBytes(int index, byte[] src, int srcOffset, int length) {
        unsafe.copyMemory(src, ARRAY_BYTE_BASE_OFFSET + srcOffset, base, address+index, length);
    }

    public void putByteBuffer(int index, ByteBuffer src, int len) {
        assert(len <= src.remaining());

        if(src.isDirect()) {
            DirectBuffer db = (DirectBuffer) src;
            unsafe.copyMemory(null, db.address() + src.position(), base, address+index, len);
        } else if(src.hasArray()) {
            byte[] srcArray = src.array();
            unsafe.copyMemory(srcArray, ARRAY_BYTE_BASE_OFFSET + src.position(), base, address+index, len);
        } else {
            if(base != null) {
                src.get((byte []) base, index, len);
            }
            else {
                for(int i=0; i<len; ++i) {
                    unsafe.putByte(base, address + index, src.get());
                }
            }
        }
        src.position(src.position() + len);
    }


    /**
     * Create a ByteBuffer view of the range [index, index+length) of this memory
     * @param index
     * @param length
     * @return
     */
    public ByteBuffer toByteBuffer(int index, int length) {
        if(hasArray()) {
            return ByteBuffer.wrap((byte[]) base, (int) ((address-ARRAY_BYTE_BASE_OFFSET) + index), length);
        }
        try {
            if(isByteBufferConstructorTakesBufferReference)
                return (ByteBuffer) byteBufferConstructor.newInstance(address + index, length, reference);
            else
                return (ByteBuffer) byteBufferConstructor.newInstance(address + index, length);
        } catch(Throwable e) {
            // Convert checked exception to unchecked exception
            throw new RuntimeException(e);
        }
    }

    /**
     * Get a ByteBuffer view of this buffer
     * @return
     */
    public ByteBuffer toByteBuffer() {
        return toByteBuffer(0, size());
    }

    /**
     * Get a copy of this buffer
     * @return
     */
    public byte[] toByteArray() {
        byte[] b = new byte[size()];
        unsafe.copyMemory(base, address, b, ARRAY_BYTE_BASE_OFFSET, size());
        return b;
    }

    @Insecure
    public boolean hasArray() { return base instanceof byte[]; }

    @Insecure
    public byte[] getArray() { return (byte[]) base; }

    @Insecure
    public Object getBase() { return base; }

    @Insecure
    public long getAddress() { return address; }

    @Insecure
    public int offset() {
        if(hasArray()) {
            return (int) address - ARRAY_BYTE_BASE_OFFSET;
        }
        else {
            return 0;
        }
    }

    @Insecure
    public Object getReference() { return reference; }


    public void relocate(int offset, int length, int dst) {
        unsafe.copyMemory(base, address + offset, base, address+dst, length);
    }

    /**
     * Copy this buffer contents to another MessageBuffer
     * @param index
     * @param dst
     * @param offset
     * @param length
     */
    public void copyTo(int index, MessageBuffer dst, int offset, int length) {
        unsafe.copyMemory(base, address + index, dst.base, dst.address + offset, length);
    }


    public String toHexString(int offset, int length) {
        StringBuilder s = new StringBuilder();
        for(int i=offset; i<length; ++i) {
            if(i != offset)
              s.append(" ");
            s.append(String.format("%02x", getByte(i)));
        }
        return s.toString();
    }

}
TOP

Related Classes of org.msgpack.core.buffer.MessageBuffer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.