org.asynchttpclient.netty.util.Utf8ByteBufCharsetDecoder.java Source code

Java tutorial

Introduction

Here is the source code for org.asynchttpclient.netty.util.Utf8ByteBufCharsetDecoder.java

Source

/*
 * Copyright (c) 2016 AsyncHttpClient Project. All rights reserved.
 *
 * This program is licensed to you under the Apache License Version 2.0,
 * and you may not use this file except in compliance with the Apache License Version 2.0.
 * You may obtain a copy of the Apache License Version 2.0 at
 *     http://www.apache.org/licenses/LICENSE-2.0.
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the Apache License Version 2.0 is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
 */
package org.asynchttpclient.netty.util;

import static java.nio.charset.StandardCharsets.UTF_8;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;

public class Utf8ByteBufCharsetDecoder {

    private static final ThreadLocal<Utf8ByteBufCharsetDecoder> POOL = new ThreadLocal<Utf8ByteBufCharsetDecoder>() {
        protected Utf8ByteBufCharsetDecoder initialValue() {
            return new Utf8ByteBufCharsetDecoder();
        }
    };

    private static Utf8ByteBufCharsetDecoder pooledDecoder() {
        Utf8ByteBufCharsetDecoder decoder = POOL.get();
        decoder.reset();
        return decoder;
    }

    public static String decodeUtf8(ByteBuf buf) throws CharacterCodingException {
        return pooledDecoder().decode(buf);
    }

    public static String decodeUtf8(ByteBuf... bufs) throws CharacterCodingException {
        return pooledDecoder().decode(bufs);
    }

    private final CharsetDecoder decoder = UTF_8.newDecoder();
    protected CharBuffer charBuffer = allocateCharBuffer(1024);
    private ByteBuffer splitCharBuffer;

    protected void initSplitCharBuffer() {
        if (splitCharBuffer == null) {
            // UTF-8 chars are 4 bytes max
            splitCharBuffer = ByteBuffer.allocate(4);
        }
    }

    protected CharBuffer allocateCharBuffer(int l) {
        return CharBuffer.allocate(l);
    }

    private void ensureCapacity(int l) {
        if (charBuffer.position() == 0) {
            if (charBuffer.capacity() < l) {
                charBuffer = allocateCharBuffer(l);
            }
        } else if (charBuffer.remaining() < l) {
            CharBuffer newCharBuffer = allocateCharBuffer(charBuffer.position() + l);
            charBuffer.flip();
            newCharBuffer.put(charBuffer);
            charBuffer = newCharBuffer;
        }
    }

    public void reset() {
        decoder.reset();
        charBuffer.position(0);
    }

    private static int charSize(byte firstByte) throws CharacterCodingException {
        if ((firstByte >> 5) == -2 && (firstByte & 0x1e) != 0) {
            // 2 bytes, 11 bits: 110xxxxx 10xxxxxx
            return 2;

        } else if ((firstByte >> 4) == -2) {
            // 3 bytes, 16 bits: 1110xxxx 10xxxxxx 10xxxxxx
            return 3;

        } else if ((firstByte >> 3) == -2) {
            // 4 bytes, 21 bits: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
            return 4;

        } else {
            // charSize isn't supposed to be called for regular bytes
            throw new CharacterCodingException();
        }
    }

    private void handleSplitCharBuffer(ByteBuffer nioBuffer, boolean endOfInput) throws CharacterCodingException {
        // TODO we could save charSize
        int missingBytes = charSize(splitCharBuffer.get(0)) - splitCharBuffer.position();

        if (nioBuffer.remaining() < missingBytes) {
            if (endOfInput) {
                throw new CharacterCodingException();
            }

            // still not enough bytes
            splitCharBuffer.put(nioBuffer);

        } else {
            // FIXME better way?
            for (int i = 0; i < missingBytes; i++) {
                splitCharBuffer.put(nioBuffer.get());
            }

            splitCharBuffer.flip();
            CoderResult res = decoder.decode(splitCharBuffer, charBuffer, endOfInput && !nioBuffer.hasRemaining());
            if (res.isError()) {
                res.throwException();
            }

            splitCharBuffer.position(0);
        }
    }

    protected void decodePartial(ByteBuffer nioBuffer, boolean endOfInput) throws CharacterCodingException {
        // deal with pending splitCharBuffer
        if (splitCharBuffer != null && splitCharBuffer.position() > 0 && nioBuffer.hasRemaining()) {
            handleSplitCharBuffer(nioBuffer, endOfInput);
        }

        // decode remaining buffer
        if (nioBuffer.hasRemaining()) {
            CoderResult res = decoder.decode(nioBuffer, charBuffer, endOfInput);
            if (res.isUnderflow()) {
                if (nioBuffer.remaining() > 0) {
                    initSplitCharBuffer();
                    splitCharBuffer.put(nioBuffer);
                }
            } else if (res.isError()) {
                res.throwException();
            }
        }
    }

    private void decode(ByteBuffer[] nioBuffers, int length) throws CharacterCodingException {
        int count = nioBuffers.length;
        for (int i = 0; i < count; i++) {
            decodePartial(nioBuffers[i].duplicate(), i == count - 1);
        }
    }

    private void decodeSingleNioBuffer(ByteBuffer nioBuffer, int length) throws CharacterCodingException {
        CoderResult res = decoder.decode(nioBuffer, charBuffer, true);
        if (res.isError()) {
            res.throwException();
        }
    }

    public String decode(ByteBuf buf) throws CharacterCodingException {
        if (buf.isDirect()) {
            return buf.toString(UTF_8);
        }

        int length = buf.readableBytes();
        ensureCapacity(length);

        if (buf.nioBufferCount() == 1) {
            decodeSingleNioBuffer(buf.internalNioBuffer(buf.readerIndex(), length).duplicate(), length);
        } else {
            decode(buf.nioBuffers(), buf.readableBytes());
        }

        return charBuffer.flip().toString();
    }

    public String decode(ByteBuf... bufs) throws CharacterCodingException {
        if (bufs.length == 1) {
            return decode(bufs[0]);
        }

        int totalSize = 0;
        int totalNioBuffers = 0;
        boolean direct = false;
        for (ByteBuf buf : bufs) {
            if (buf.isDirect()) {
                direct = true;
                break;
            }
            totalSize += buf.readableBytes();
            totalNioBuffers += buf.nioBufferCount();
        }

        if (direct) {
            ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(bufs);
            try {
                return wrappedBuffer.toString(UTF_8);
            } finally {
                wrappedBuffer.release();
            }

        } else {
            ByteBuffer[] nioBuffers = new ByteBuffer[totalNioBuffers];
            int i = 0;
            for (ByteBuf buf : bufs) {
                for (ByteBuffer nioBuffer : buf.nioBuffers()) {
                    nioBuffers[i++] = nioBuffer;
                }
            }

            ensureCapacity(totalSize);
            decode(nioBuffers, totalSize);

            return charBuffer.flip().toString();
        }
    }
}