File: //usr/local/rvm/gems/default/gems/nio4r-2.5.8/lib/nio/bytebuffer.rb
# frozen_string_literal: true
module NIO
# Efficient byte buffers for performant I/O operations
class ByteBuffer
include Enumerable
attr_reader :position, :limit, :capacity
# Insufficient capacity in buffer
OverflowError = Class.new(IOError)
# Not enough data remaining in buffer
UnderflowError = Class.new(IOError)
# Mark has not been set
MarkUnsetError = Class.new(IOError)
# Create a new ByteBuffer, either with a specified capacity or populating
# it from a given string
#
# @param capacity [Integer] size of buffer in bytes
#
# @return [NIO::ByteBuffer]
def initialize(capacity)
raise TypeError, "no implicit conversion of #{capacity.class} to Integer" unless capacity.is_a?(Integer)
@capacity = capacity
clear
end
# Clear the buffer, resetting it to the default state
def clear
@buffer = ("\0" * @capacity).force_encoding(Encoding::BINARY)
@position = 0
@limit = @capacity
@mark = nil
self
end
# Set the position to the given value. New position must be less than limit.
# Preserves mark if it's less than the new position, otherwise clears it.
#
# @param new_position [Integer] position in the buffer
#
# @raise [ArgumentError] new position was invalid
def position=(new_position)
raise ArgumentError, "negative position given" if new_position < 0
raise ArgumentError, "specified position exceeds capacity" if new_position > @capacity
@mark = nil if @mark && @mark > new_position
@position = new_position
end
# Set the limit to the given value. New limit must be less than capacity.
# Preserves limit and mark if they're less than the new limit, otherwise
# sets position to the new limit and clears the mark.
#
# @param new_limit [Integer] position in the buffer
#
# @raise [ArgumentError] new limit was invalid
def limit=(new_limit)
raise ArgumentError, "negative limit given" if new_limit < 0
raise ArgumentError, "specified limit exceeds capacity" if new_limit > @capacity
@position = new_limit if @position > new_limit
@mark = nil if @mark && @mark > new_limit
@limit = new_limit
end
# Number of bytes remaining in the buffer before the limit
#
# @return [Integer] number of bytes remaining
def remaining
@limit - @position
end
# Does the ByteBuffer have any space remaining?
#
# @return [true, false]
def full?
remaining.zero?
end
# Obtain the requested number of bytes from the buffer, advancing the position.
# If no length is given, all remaining bytes are consumed.
#
# @raise [NIO::ByteBuffer::UnderflowError] not enough data remaining in buffer
#
# @return [String] bytes read from buffer
def get(length = remaining)
raise ArgumentError, "negative length given" if length < 0
raise UnderflowError, "not enough data in buffer" if length > @limit - @position
result = @buffer[@position...length]
@position += length
result
end
# Obtain the byte at a given index in the buffer as an Integer
#
# @raise [ArgumentError] index is invalid (either negative or larger than limit)
#
# @return [Integer] byte at the given index
def [](index)
raise ArgumentError, "negative index given" if index < 0
raise ArgumentError, "specified index exceeds limit" if index >= @limit
@buffer.bytes[index]
end
# Add a String to the buffer
#
# @param str [#to_str] data to add to the buffer
#
# @raise [TypeError] given a non-string type
# @raise [NIO::ByteBuffer::OverflowError] buffer is full
#
# @return [self]
def put(str)
raise TypeError, "expected String, got #{str.class}" unless str.respond_to?(:to_str)
str = str.to_str
raise OverflowError, "buffer is full" if str.length > @limit - @position
@buffer[@position...str.length] = str
@position += str.length
self
end
alias << put
# Perform a non-blocking read from the given IO object into the buffer
# Reads as much data as is immediately available and returns
#
# @param [IO] Ruby IO object to read from
#
# @return [Integer] number of bytes read (0 if none were available)
def read_from(io)
nbytes = @limit - @position
raise OverflowError, "buffer is full" if nbytes.zero?
bytes_read = IO.try_convert(io).read_nonblock(nbytes, exception: false)
return 0 if bytes_read == :wait_readable
self << bytes_read
bytes_read.length
end
# Perform a non-blocking write of the buffer's contents to the given I/O object
# Writes as much data as is immediately possible and returns
#
# @param [IO] Ruby IO object to write to
#
# @return [Integer] number of bytes written (0 if the write would block)
def write_to(io)
nbytes = @limit - @position
raise UnderflowError, "no data remaining in buffer" if nbytes.zero?
bytes_written = IO.try_convert(io).write_nonblock(@buffer[@position...@limit], exception: false)
return 0 if bytes_written == :wait_writable
@position += bytes_written
bytes_written
end
# Set the buffer's current position as the limit and set the position to 0
def flip
@limit = @position
@position = 0
@mark = nil
self
end
# Set the buffer's current position to 0, leaving the limit unchanged
def rewind
@position = 0
@mark = nil
self
end
# Mark a position to return to using the `#reset` method
def mark
@mark = @position
self
end
# Reset position to the previously marked location
#
# @raise [NIO::ByteBuffer::MarkUnsetError] mark has not been set (call `#mark` first)
def reset
raise MarkUnsetError, "mark has not been set" unless @mark
@position = @mark
self
end
# Move data between the position and limit to the beginning of the buffer
# Sets the position to the end of the moved data, and the limit to the capacity
def compact
@buffer[0...(@limit - @position)] = @buffer[@position...@limit]
@position = @limit - @position
@limit = capacity
self
end
# Iterate over the bytes in the buffer (as Integers)
#
# @return [self]
def each(&block)
@buffer[0...@limit].each_byte(&block)
end
# Inspect the state of the buffer
#
# @return [String] string describing the state of the buffer
def inspect
format(
"#<%s:0x%x @position=%d @limit=%d @capacity=%d>",
self.class,
object_id << 1,
@position,
@limit,
@capacity
)
end
end
end