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

com.cinchapi.common.io.ByteBuffers Maven / Gradle / Ivy

Go to download

Accent4J is a suite of libraries, helpers and data structures that make Java programming idioms more fluent.

There is a newer version: 1.13.1
Show newest version
/*
 * Copyright (c) 2013-2015 Cinchapi Inc.
 * 
 * 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 com.cinchapi.common.io;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;

import com.cinchapi.common.base.CheckedExceptions;
import com.cinchapi.common.base.Verify;
import com.cinchapi.common.collect.concurrent.ConcurrentLoadingQueue;

/**
 * Additional utility methods for ByteBuffers that are not found in the
 * {@link ByteBuffer} class.
 * 
 * @author Jeff Nelson
 */
public abstract class ByteBuffers {

    /**
     * The name of the Charset to use for encoding/decoding. We use the name
     * instead of the charset object because Java caches encoders when
     * referencing them by name, but creates a new encorder object when
     * referencing them by Charset object.
     */
    private static final String CHARSET = StandardCharsets.UTF_8.name();

    /**
     * A collection of UTF-8 decoders that can be concurrently used. We use this
     * to avoid creating a new decoder every time we need to decode a string
     * while still allowing multi-threaded access.
     */
    private static final ConcurrentLinkedQueue DECODERS = ConcurrentLoadingQueue
            .create(new Callable() {

                @Override
                public CharsetDecoder call() throws Exception {
                    return StandardCharsets.UTF_8.newDecoder();
                }

            });

    /**
     * The number of UTF-8 decoders to create for concurrent access.
     */
    private static final int NUM_DECODERS = 10;

    static {
        try {
            for (int i = 0; i < NUM_DECODERS; ++i) {
                DECODERS.add(StandardCharsets.UTF_8.newDecoder());
            }
        }
        catch (Exception e) {
            throw CheckedExceptions.throwAsRuntimeException(e);
        }
    }

    /**
     * Return a byte array containing all the {@link ByteBuffer#remaining()}
     * content within the {@code buffer}. The {@code buffer}'s mark is set to
     * the current position. The {@code buffer}'s limit is preserved.
     * 
     * @param buffer
     * @return a byte array containing the remaining the content in the
     *         {@code buffer}.
     */
    public static byte[] getByteArray(ByteBuffer buffer) {
        buffer.mark();
        byte[] array = new byte[buffer.remaining()];
        buffer.get(array);
        buffer.reset();
        return array;
    }

    /**
     * Return a ByteBuffer that is a new read-only buffer that shares the
     * content of {@code source} and has the same byte order, but maintains a
     * distinct position, mark and limit.
     * 
     * @param source
     * @return the new, read-only byte buffer
     */
    public static ByteBuffer asReadOnlyBuffer(ByteBuffer source) {
        int position = source.position();
        source.rewind();
        ByteBuffer duplicate = source.asReadOnlyBuffer();
        duplicate.order(source.order()); // byte order is not natively preserved
                                         // when making duplicates:
                                         // http://blog.mustardgrain.com/2008/04/04/bytebufferduplicate-does-not-preserve-byte-order/
        source.position(position);
        duplicate.rewind();
        return duplicate;
    }

    /**
     * Return a clone of {@code buffer} that has a copy of all its
     * content and the same position and limit. Unlike the
     * {@link ByteBuffer#slice()} method, the returned clone
     * does not share its content with {@code buffer}, so
     * subsequent operations to {@code buffer} or its clone will be
     * completely independent and won't affect the other.
     * 
     * @param buffer
     * @return a clone of {@code buffer}
     */
    public static ByteBuffer clone(ByteBuffer buffer) {
        ByteBuffer clone = ByteBuffer.allocate(buffer.capacity());
        int position = buffer.position();
        int limit = buffer.limit();
        buffer.rewind();
        clone.put(buffer);
        buffer.position(position);
        clone.position(position);
        buffer.limit(limit);
        clone.limit(limit);
        return clone;
    }

    /**
     * Transfer the bytes from {@code source} to {@code destination} and resets
     * {@code source} so that its position remains unchanged. The position of
     * the {@code destination} is incremented by the number of bytes that are
     * transferred.
     * 
     * @param source
     * @param destination
     */
    public static void copyAndRewindSource(ByteBuffer source,
            ByteBuffer destination) {
        int position = source.position();
        destination.put(source);
        source.position(position);
    }

    /**
     * Decode the {@code hex}adeciaml string and return the resulting binary
     * data.
     * 
     * @param hex
     * @return the binary data
     */
    public static ByteBuffer decodeFromHex(String hex) {
        int len = hex.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
                    + Character.digit(hex.charAt(i + 1), 16));
        }
        return ByteBuffer.wrap(data);
    }

    /**
     * Encode the remaining bytes in as {@link ByteBuffer} as a hex string and
     * maintain the current position.
     * 
     * @param buffer
     * @return the hex string
     */
    public static String encodeAsHex(ByteBuffer buffer) {
        StringBuilder sb = new StringBuilder();
        buffer.mark();
        while (buffer.hasRemaining()) {
            sb.append(String.format("%02x", buffer.get()));
        }
        buffer.reset();
        return sb.toString();
    }

    /**
     * Return a byte buffer that has the UTF-8 encoding for {@code string}. This
     * method uses some optimization techniques and is the preferable way to
     * convert strings to byte buffers than doing so manually.
     * 
     * @param string
     * @return the byte buffer with the {@code string} data.
     */
    public static ByteBuffer fromUtf8String(String string) {
        try {
            return ByteBuffer.wrap(string.getBytes(CHARSET));
        }
        catch (Exception e) {
            throw CheckedExceptions.throwAsRuntimeException(e);
        }
    }

    /**
     * Return a ByteBuffer that has a copy of {@code length} bytes from
     * {@code buffer} starting from the current position. This method will
     * advance the position of the source buffer.
     * 
     * @param buffer
     * @param length
     * @return a ByteBuffer that has {@code length} bytes from {@code buffer}
     */
    public static ByteBuffer get(ByteBuffer buffer, int length) {
        Verify.thatArgument(buffer.remaining() >= length,
                "The number of bytes remaining in the buffer cannot be less than the desired length");
        byte[] backingArray = new byte[length];
        buffer.get(backingArray);
        return ByteBuffer.wrap(backingArray);
    }

    /**
     * Relative get method. Reads the byte at the current position in
     * {@code buffer} as a boolean, and then increments the position.
     * 
     * @param buffer
     * @return the boolean value at the current position
     */
    public static boolean getBoolean(ByteBuffer buffer) {
        return buffer.get() > 0 ? true : false;
    }

    /**
     * Relative get method. Reads the enum at the current position in
     * {@code buffer} and then increments the position by four.
     * 
     * @param buffer
     * @param clazz
     * @return the enum value at the current position
     */
    public static > T getEnum(ByteBuffer buffer,
            Class clazz) {
        return clazz.getEnumConstants()[buffer.getInt()];
    }

    /**
     * Relative get method. Reads the {@code charset} encoded string at
     * the current position in {@code buffer}.
     * 
     * @param buffer
     * @param charset
     * @return the string value at the current position
     */
    public static String getString(ByteBuffer buffer, Charset charset) {
        CharsetDecoder decoder = null;
        try {
            if(charset == StandardCharsets.UTF_8) {
                while (decoder == null) {
                    decoder = DECODERS.poll();
                }
            }
            else {
                decoder = charset.newDecoder();
            }
            decoder.onMalformedInput(CodingErrorAction.IGNORE);
            return decoder.decode(buffer).toString();
        }
        catch (CharacterCodingException e) {
            throw CheckedExceptions.throwAsRuntimeException(e);
        }
        finally {
            if(decoder != null && charset == StandardCharsets.UTF_8) {
                DECODERS.offer(decoder);
            }
        }
    }

    /**
     * Relative get method. Reads the UTF-8 encoded string at
     * the current position in {@code buffer}.
     * 
     * @param buffer
     * @return the string value at the current position
     */
    public static String getUtf8String(ByteBuffer buffer) {
        return getString(buffer, StandardCharsets.UTF_8);
    }

    /**
     * Put the UTF-8 encoding for the {@code source} string into the
     * {@code destination} byte buffer and increment the position by the length
     * of the strings byte sequence. This method uses some optimization
     * techniques and is the preferable way to add strings to byte buffers than
     * doing so manually.
     * 
     * @param source
     * @param destination
     */
    public static void putUtf8String(String source, ByteBuffer destination) {
        try {
            byte[] bytes = source.getBytes(CHARSET);
            destination.put(bytes);
        }
        catch (Exception e) {
            throw CheckedExceptions.throwAsRuntimeException(e);
        }
    }

    /**
     * Return a new ByteBuffer whose content is a shared subsequence of the
     * content in {@code buffer} starting at the current position to
     * current position + {@code length} (non-inclusive). Invoking this method
     * has the same affect as doing the following:
     * 
     * 
     * buffer.mark();
     * int oldLimit = buffer.limit();
     * buffer.limit(buffer.position() + length);
     * 
     * ByteBuffer slice = buffer.slice();
     * 
     * buffer.reset();
     * buffer.limit(oldLimit);
     * 
* * @param buffer * @param length * @return the new ByteBuffer slice * @see ByteBuffer#slice() */ public static ByteBuffer slice(ByteBuffer buffer, int length) { return slice(buffer, buffer.position(), length); } /** * Return a new ByteBuffer whose content is a shared subsequence of the * content in {@code buffer} starting at {@code position} to * {@code position} + {@code length} (non-inclusive). Invoking this method * has the same affect as doing the following: * *
     * buffer.mark();
     * int oldLimit = buffer.limit();
     * buffer.position(position);
     * buffer.limit(position + length);
     * 
     * ByteBuffer slice = buffer.slice();
     * 
     * buffer.reset();
     * buffer.limit(oldLimit);
     * 
* * @param buffer * @param position * @param length * @return the new ByteBuffer slice * @see ByteBuffer#slice() */ public static ByteBuffer slice(ByteBuffer buffer, int position, int length) { int oldPosition = buffer.position(); int oldLimit = buffer.limit(); buffer.position(position); buffer.limit(position + length); ByteBuffer slice = buffer.slice(); buffer.limit(oldLimit); buffer.position(oldPosition); return slice; } /** * Return a byte array with the content of {@code buffer}. This method * returns the byte array that backs {@code buffer} if one exists, otherwise * it creates a new byte array with the content between the current position * of {@code buffer} and its limit. * * @param buffer * @return the byte array with the content of {@code buffer} * @deprecated in favor of {@link #array(ByteBuffer)} because this method * returns the entire backing byte array instead of a byte array * only containing the remaining content if the buffer is backed * by a byte array */ @Deprecated public static byte[] toByteArray(ByteBuffer buffer) { if(buffer.hasArray()) { return buffer.array(); } else { buffer.mark(); byte[] array = new byte[buffer.remaining()]; buffer.get(array); buffer.reset(); return array; } } /** * Return a {@link CharBuffer} representation of the bytes in the * {@code buffer} encoded with the {@code charset}. * * @param buffer * @param charset * @return the char buffer */ public static CharBuffer toCharBuffer(ByteBuffer buffer, Charset charset) { buffer.mark(); CharBuffer chars = charset.decode(buffer); buffer.reset(); return chars; } /** * Return a UTF-8 {@link CharBuffer} representation of the bytes in the * {@code buffer}. * * @param buffer * @return the char buffer */ public static CharBuffer toUtf8CharBuffer(ByteBuffer buffer) { return toCharBuffer(buffer, StandardCharsets.UTF_8); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy