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

com.cinchapi.concourse.util.ByteBuffers Maven / Gradle / Ivy

/*
 * Copyright (c) 2013-2017 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.concourse.util;

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.ConcurrentLinkedQueue;

import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.io.BaseEncoding;

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

    /**
     * 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) {
        return ByteBuffer.wrap(BaseEncoding.base16().decode(hex));
    }

    /**
     * Encode the {@code bytes} as a hexadecimal string.
     * 
     * @param bytes
     * @return the hex string
     */
    public static String encodeAsHex(ByteBuffer bytes) {
        bytes.rewind();
        return BaseEncoding.base16().encode(ByteBuffers.toByteArray(bytes));
    }

    /**
     * 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 encodeAsHexString(ByteBuffer buffer) {
        StringBuilder sb = new StringBuilder();
        buffer.mark();
        while (buffer.hasRemaining()) {
            sb.append(String.format("%02x", buffer.get()));
        }
        buffer.reset();
        return sb.toString();
    }

    /**
     * Copy the remaining bytes in the {@code source} buffer to the
     * {@code destination}, expanding if necessary in
     * order to accommodate the bytes from {@code source}.
     * 
     * 

* NOTE: This method may modify the {@code limit} for the * destination buffer. *

* * @param destination the buffer into which the {@code source} is copied * @param source the buffer that is copied into the {@code destination} * @return a possibly expanded copy of {@code destination} with the * {@code source} bytes copied */ public static ByteBuffer expand(ByteBuffer destination, ByteBuffer source) { destination = ensureRemainingCapacity(destination, source.remaining()); int newLimit = destination.position() + source.remaining(); if(destination.limit() < newLimit) { destination.limit(newLimit); } destination.put(source); return destination; } /** * Put the {@code value} into {@code destination}, expanding if necessary in * order to accommodate the new bytes. * *

* NOTE: This method may modify the {@code limit} for the * destination buffer. *

* * @param destination the buffer into which the {@code source} is copied * @param value the value to add to the {@code destination} * @return a possibly expanded copy of {@code destination} with the * {@code value} bytes copied */ public static ByteBuffer expandInt(ByteBuffer destination, int value) { destination = ensureRemainingCapacity(destination, 4); int newLimit = destination.position() + 4; if(destination.limit() < newLimit) { destination.limit(newLimit); } destination.putInt(value); return destination; } /** * 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 fromString(String string) { try { return ByteBuffer.wrap(string.getBytes(CHARSET)); } catch (Exception e) { throw Throwables.propagate(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) { Preconditions.checkArgument(buffer.remaining() >= length, "The number of bytes remaining in the buffer cannot be less than 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()]; } /** * Return a ByteBuffer that has a copy of all the remaining bytes from * {@code buffer} starting from the current position. This method will * advance the position of the source buffer. * * @param buffer the source buffer * @return a ByteBuffer that has the remaining bytes from {@code buffer} */ public static ByteBuffer getRemaining(ByteBuffer buffer) { return get(buffer, buffer.remaining()); } /** * 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 getString(ByteBuffer buffer) { return getString(buffer, StandardCharsets.UTF_8); } /** * 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 Throwables.propagate(e); } finally { if(decoder != null && charset == StandardCharsets.UTF_8) { DECODERS.offer(decoder); } } } /** * Return a ByteBuffer that contains a single null byte. * * @return a null byte buffer */ public static ByteBuffer nullByteBuffer() { ByteBuffer nullByte = ByteBuffer.allocate(1); nullByte.put((byte) 0); nullByte.rewind(); return nullByte; } /** * 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 putString(String source, ByteBuffer destination) { try { byte[] bytes = source.getBytes(CHARSET); destination.put(bytes); } catch (Exception e) { throw Throwables.propagate(e); } } /** * The exact same as {@link ByteBuffer#rewind()} except it returns a typed * {@link ByteBuffer} instead of a generic {@link Buffer}. * * @param buffer * @return {@code buffer} */ public static ByteBuffer rewind(ByteBuffer buffer) { buffer.rewind(); return buffer; } /** * 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} */ 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 UTF-8 {@link CharBuffer} representation of the bytes in the * {@code buffer}. * * @param buffer * @return the char buffer */ public static CharBuffer toCharBuffer(ByteBuffer buffer) { return toCharBuffer(buffer, StandardCharsets.UTF_8); } /** * 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; } /** * Ensure that {@code buffer} has {@code capacity} bytes * {@link ByteBuffer#remaining() remaining} and return either {@code buffer} * or a copy that has enough capacity. * * @param buffer the buffer to check for remaining capacity * @param capacity the number of bytes required * @return a {@link ByteBuffer} with all the contents of {@code buffer} and * enough remaining room for {@code capacity} bytes */ private static ByteBuffer ensureRemainingCapacity(ByteBuffer buffer, int capacity) { if((buffer.capacity() - buffer.position()) < capacity) { ByteBuffer copy = ByteBuffer .allocate(((buffer.capacity() + capacity) * 3) / 2 + 1); buffer.limit(buffer.position()); buffer.rewind(); copy.put(buffer); buffer = copy; } return buffer; } /** * 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 = new ConcurrentLinkedQueue(); /** * 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 Throwables.propagate(e); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy