com.buaa.cfs.common.oncrpc.XDR.java Source code

Java tutorial

Introduction

Here is the source code for com.buaa.cfs.common.oncrpc.XDR.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE
 * file distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file
 * to you 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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 com.buaa.cfs.common.oncrpc;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import org.apache.commons.io.Charsets;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;

import java.nio.ByteBuffer;

/**
 * Utility class for building XDR messages based on RFC 4506.
 *
 * Key points of the format:
 *
 * <ul>
 * <li>Primitives are stored in big-endian order (i.e., the default byte order
 * of ByteBuffer).</li>
 * <li>Booleans are stored as an integer.</li>
 * <li>Each field in the message is always aligned by 4.</li>
 * </ul>
 *
 */
public final class XDR {
    private static final int DEFAULT_INITIAL_CAPACITY = 256;
    private static final int SIZEOF_INT = 4;
    private static final int SIZEOF_LONG = 8;
    private static final byte[] PADDING_BYTES = new byte[] { 0, 0, 0, 0 };

    private ByteBuffer buf;

    public enum State {
        READING, WRITING,
    }

    private final State state;

    /**
     * Construct a new XDR message buffer.
     *
     * @param initialCapacity
     *          the initial capacity of the buffer.
     */
    public XDR(int initialCapacity) {
        this(ByteBuffer.allocate(initialCapacity), State.WRITING);
    }

    public XDR() {
        this(DEFAULT_INITIAL_CAPACITY);
    }

    public XDR(ByteBuffer buf, State state) {
        this.buf = buf;
        this.state = state;
    }

    /**
     * Wraps a byte array as a read-only XDR message. There's no copy involved,
     * thus it is the client's responsibility to ensure that the byte array
     * remains unmodified when using the XDR object.
     *
     * @param src
     *          the byte array to be wrapped.
     */
    public XDR(byte[] src) {
        this(ByteBuffer.wrap(src).asReadOnlyBuffer(), State.READING);
    }

    public XDR asReadOnlyWrap() {
        ByteBuffer b = buf.asReadOnlyBuffer();
        if (state == State.WRITING) {
            b.flip();
        }

        XDR n = new XDR(b, State.READING);
        return n;
    }

    public ByteBuffer buffer() {
        return buf.duplicate();
    }

    public int size() {
        // TODO: This overloading intends to be compatible with the semantics of
        // the previous version of the class. This function should be separated into
        // two with clear semantics.
        return state == State.READING ? buf.limit() : buf.position();
    }

    public int readInt() {
        Preconditions.checkState(state == State.READING);
        return buf.getInt();
    }

    public void writeInt(int v) {
        ensureFreeSpace(SIZEOF_INT);
        buf.putInt(v);
    }

    public boolean readBoolean() {
        Preconditions.checkState(state == State.READING);
        return buf.getInt() != 0;
    }

    public void writeBoolean(boolean v) {
        ensureFreeSpace(SIZEOF_INT);
        buf.putInt(v ? 1 : 0);
    }

    public long readHyper() {
        Preconditions.checkState(state == State.READING);
        return buf.getLong();
    }

    public void writeLongAsHyper(long v) {
        ensureFreeSpace(SIZEOF_LONG);
        buf.putLong(v);
    }

    public byte[] readFixedOpaque(int size) {
        Preconditions.checkState(state == State.READING);
        byte[] r = new byte[size];
        buf.get(r);
        alignPosition();
        return r;
    }

    public void writeFixedOpaque(byte[] src, int length) {
        ensureFreeSpace(alignUp(length));
        buf.put(src, 0, length);
        writePadding();
    }

    public void writeFixedOpaque(byte[] src) {
        writeFixedOpaque(src, src.length);
    }

    public byte[] readVariableOpaque() {
        Preconditions.checkState(state == State.READING);
        int size = readInt();
        return readFixedOpaque(size);
    }

    public void writeVariableOpaque(byte[] src) {
        ensureFreeSpace(SIZEOF_INT + alignUp(src.length));
        buf.putInt(src.length);
        writeFixedOpaque(src);
    }

    public String readString() {
        return new String(readVariableOpaque(), Charsets.UTF_8);
    }

    public void writeString(String s) {
        writeVariableOpaque(s.getBytes(Charsets.UTF_8));
    }

    private void writePadding() {
        Preconditions.checkState(state == State.WRITING);
        int p = pad(buf.position());
        ensureFreeSpace(p);
        buf.put(PADDING_BYTES, 0, p);
    }

    private int alignUp(int length) {
        return length + pad(length);
    }

    private int pad(int length) {
        switch (length % 4) {
        case 1:
            return 3;
        case 2:
            return 2;
        case 3:
            return 1;
        default:
            return 0;
        }
    }

    private void alignPosition() {
        buf.position(alignUp(buf.position()));
    }

    private void ensureFreeSpace(int size) {
        Preconditions.checkState(state == State.WRITING);
        if (buf.remaining() < size) {
            int newCapacity = buf.capacity() * 2;
            int newRemaining = buf.capacity() + buf.remaining();

            while (newRemaining < size) {
                newRemaining += newCapacity;
                newCapacity *= 2;
            }

            ByteBuffer newbuf = ByteBuffer.allocate(newCapacity);
            buf.flip();
            newbuf.put(buf);
            buf = newbuf;
        }
    }

    /** check if the rest of data has more than len bytes */
    public static boolean verifyLength(XDR xdr, int len) {
        return xdr.buf.remaining() >= len;
    }

    static byte[] recordMark(int size, boolean last) {
        byte[] b = new byte[SIZEOF_INT];
        ByteBuffer buf = ByteBuffer.wrap(b);
        buf.putInt(!last ? size : size | 0x80000000);
        return b;
    }

    /** Write an XDR message to a TCP ChannelBuffer */
    public static ChannelBuffer writeMessageTcp(XDR request, boolean last) {
        Preconditions.checkState(request.state == XDR.State.WRITING);
        ByteBuffer b = request.buf.duplicate();
        b.flip();
        byte[] fragmentHeader = XDR.recordMark(b.limit(), last);
        ByteBuffer headerBuf = ByteBuffer.wrap(fragmentHeader);

        // TODO: Investigate whether making a copy of the buffer is necessary.
        return ChannelBuffers.copiedBuffer(headerBuf, b);
    }

    /** Write an XDR message to a UDP ChannelBuffer */
    public static ChannelBuffer writeMessageUdp(XDR response) {
        Preconditions.checkState(response.state == XDR.State.READING);
        // TODO: Investigate whether making a copy of the buffer is necessary.
        return ChannelBuffers.copiedBuffer(response.buf);
    }

    public static int fragmentSize(byte[] mark) {
        ByteBuffer b = ByteBuffer.wrap(mark);
        int n = b.getInt();
        return n & 0x7fffffff;
    }

    public static boolean isLastFragment(byte[] mark) {
        ByteBuffer b = ByteBuffer.wrap(mark);
        int n = b.getInt();
        return (n & 0x80000000) != 0;
    }

    @VisibleForTesting
    public byte[] getBytes() {
        ByteBuffer d = asReadOnlyWrap().buffer();
        byte[] b = new byte[d.remaining()];
        d.get(b);

        return b;
    }
}