From 4754d518682be1ede973de8bb7e67053bc8b3f2e Mon Sep 17 00:00:00 2001 From: UpeksheJay Date: Sun, 4 Sep 2016 11:07:55 -0700 Subject: [PATCH] ByteBuffers GSoC project --- ext/nio4r/bytebuffer.c | 413 ++++++++++++++++++++++++++++ ext/nio4r/nio4r.h | 8 + ext/nio4r/nio4r_ext.c | 2 + ext/nio4r/org/nio4r/ByteBuffer.java | 283 +++++++++++++++++++ ext/nio4r/org/nio4r/Nio4r.java | 9 + lib/nio.rb | 1 + lib/nio/bytebuffer.rb | 175 ++++++++++++ spec/nio/bytebuffer_spec.rb | 77 ++++++ 8 files changed, 968 insertions(+) create mode 100644 ext/nio4r/bytebuffer.c create mode 100644 ext/nio4r/org/nio4r/ByteBuffer.java create mode 100644 lib/nio/bytebuffer.rb create mode 100644 spec/nio/bytebuffer_spec.rb diff --git a/ext/nio4r/bytebuffer.c b/ext/nio4r/bytebuffer.c new file mode 100644 index 0000000..e52546c --- /dev/null +++ b/ext/nio4r/bytebuffer.c @@ -0,0 +1,413 @@ +#include "nio4r.h" + +static VALUE mNIO = Qnil; +static VALUE cNIO_ByteBuffer = Qnil; + +/* Allocator/deallocator */ +static VALUE NIO_ByteBuffer_allocate(VALUE klass); +static void NIO_ByteBuffer_mark(struct NIO_ByteBuffer *byteBuffer); +static void NIO_ByteBuffer_free(struct NIO_ByteBuffer *byteBuffer); + +/* Methods */ +static VALUE NIO_ByteBuffer_initialize(VALUE self, VALUE value, VALUE offset, VALUE length); +static VALUE NIO_ByteBuffer_put(VALUE self, VALUE string); +static VALUE NIO_ByteBuffer_get(VALUE self); +static VALUE NIO_ByteBuffer_readnext(VALUE self, VALUE amount); +static VALUE NIO_ByteBuffer_writeTo(VALUE self, VALUE file); +static VALUE NIO_ByteBuffer_readFrom(VALUE self, VALUE file); +static VALUE NIO_ByteBuffer_remaining(VALUE self); +static VALUE NIO_ByteBuffer_hasRemaining(VALUE self); +static VALUE NIO_ByteBuffer_getOffset(VALUE self); +static VALUE NIO_ByteBuffer_equals(VALUE self, VALUE other); +static VALUE NIO_ByteBuffer_flip(VALUE self); +static VALUE NIO_ByteBuffer_rewind(VALUE self); +static VALUE NIO_ByteBuffer_reset(VALUE self); +static VALUE NIO_ByteBuffer_markBuffer(VALUE self); +static VALUE NIO_ByteBuffer_clear(VALUE self); +static VALUE NIO_ByteBuffer_compact(VALUE self); +static VALUE NIO_ByteBuffer_capacity(VALUE self); +static VALUE NIO_ByteBuffer_position(VALUE self, VALUE newPosition); +static VALUE NIO_ByteBuffer_setLimit(VALUE self, VALUE newLimit); +static VALUE NIO_ByteBuffer_getLimit(VALUE self); +static VALUE NIO_ByteBuffer_toString(VALUE self); + +void Init_NIO_ByteBuffer() +{ + mNIO = rb_define_module("NIO"); + cNIO_ByteBuffer = rb_define_class_under(mNIO, "ByteBuffer", rb_cObject); + rb_define_alloc_func(cNIO_ByteBuffer, NIO_ByteBuffer_allocate); + + rb_define_method(cNIO_ByteBuffer, "initialize", NIO_ByteBuffer_initialize, 3); + rb_define_method(cNIO_ByteBuffer, "<<", NIO_ByteBuffer_put, 1); + rb_define_method(cNIO_ByteBuffer, "get", NIO_ByteBuffer_get, 0); + rb_define_method(cNIO_ByteBuffer, "read_next", NIO_ByteBuffer_readnext, 1); + rb_define_method(cNIO_ByteBuffer, "write_to", NIO_ByteBuffer_writeTo, 1); + rb_define_method(cNIO_ByteBuffer, "read_from", NIO_ByteBuffer_readFrom, 1); + rb_define_method(cNIO_ByteBuffer, "remaining", NIO_ByteBuffer_remaining, 0); + rb_define_method(cNIO_ByteBuffer, "remaining?", NIO_ByteBuffer_hasRemaining, 0); + rb_define_method(cNIO_ByteBuffer, "offset?", NIO_ByteBuffer_getOffset, 0); + rb_define_method(cNIO_ByteBuffer, "equals", NIO_ByteBuffer_equals, 1); + rb_define_method(cNIO_ByteBuffer, "flip", NIO_ByteBuffer_flip, 0); + rb_define_method(cNIO_ByteBuffer, "rewind", NIO_ByteBuffer_rewind, 0); + rb_define_method(cNIO_ByteBuffer, "reset", NIO_ByteBuffer_reset, 0); + rb_define_method(cNIO_ByteBuffer, "mark", NIO_ByteBuffer_markBuffer, 0); + rb_define_method(cNIO_ByteBuffer, "clear", NIO_ByteBuffer_clear, 0); + rb_define_method(cNIO_ByteBuffer, "compact", NIO_ByteBuffer_compact, 0); + rb_define_method(cNIO_ByteBuffer, "capacity", NIO_ByteBuffer_capacity, 0); + rb_define_method(cNIO_ByteBuffer, "position", NIO_ByteBuffer_position, 1); + rb_define_method(cNIO_ByteBuffer, "limit", NIO_ByteBuffer_setLimit, 1); + rb_define_method(cNIO_ByteBuffer, "limit?", NIO_ByteBuffer_getLimit, 0); + rb_define_method(cNIO_ByteBuffer, "to_s", NIO_ByteBuffer_toString, 0); +} + +static VALUE NIO_ByteBuffer_allocate(VALUE klass) +{ + struct NIO_ByteBuffer *bytebuffer = (struct NIO_ByteBuffer *)xmalloc(sizeof(struct NIO_ByteBuffer)); + return Data_Wrap_Struct(klass, NIO_ByteBuffer_mark, NIO_ByteBuffer_free, bytebuffer); +} + +static void NIO_ByteBuffer_mark(struct NIO_ByteBuffer *buffer) +{ +} + +static void NIO_ByteBuffer_free(struct NIO_ByteBuffer *buffer) +{ + xfree(buffer); +} + +static VALUE NIO_ByteBuffer_initialize(VALUE self, VALUE value, VALUE i_offset, VALUE i_length) +{ + //Value can be either. + //NUM -> Size of the buffer + //STRING -> Data to be stored on the buffer + struct NIO_ByteBuffer *byteBuffer; + Data_Get_Struct(self, struct NIO_ByteBuffer, byteBuffer); + + switch (TYPE(value)) { + case T_FIXNUM: + byteBuffer->size = NUM2INT(value); + byteBuffer->buffer = malloc(sizeof(char) * byteBuffer->size); + break; + case T_STRING: + byteBuffer->size = RSTRING_LEN(value); + byteBuffer->buffer = StringValuePtr(value); + //buffer = RSTRING_PTR(str); + break; + default: + /* raise exception */ + rb_raise(rb_eTypeError, "not a valid input"); + break; + } + + byteBuffer->position = 0; + byteBuffer->offset = 0; + byteBuffer->limit = byteBuffer->size - 1; + byteBuffer->self = self; + + if(i_offset != Qnil && TYPE(i_offset) == T_FIXNUM) { + byteBuffer->offset = NUM2INT(i_offset); + byteBuffer->position = byteBuffer->offset; + + if(i_length != Qnil && TYPE(i_length) == T_FIXNUM) { + int length = NUM2INT(i_length); + + if(byteBuffer->offset + length < byteBuffer->size) { + byteBuffer->limit = byteBuffer->offset + length; + } else { + rb_raise(rb_eRuntimeError, "Invalid Arguiments Exception"); + } + } + } + + if(byteBuffer->size == 0) { + rb_raise(rb_eRuntimeError, "Invalid Arguiments Exception"); + } + + return self; +} + +static VALUE NIO_ByteBuffer_put(VALUE self, VALUE string) +{ + struct NIO_ByteBuffer *byteBuffer; + Data_Get_Struct(self, struct NIO_ByteBuffer, byteBuffer); + + if(TYPE(string) == T_STRING) { + char *temp = StringValuePtr(string); + int i = 0; + int limit = RSTRING_LEN(string); + + for(byteBuffer->position; i < limit; byteBuffer->position++) { + byteBuffer->buffer[byteBuffer->position] = temp[i++]; + } + } + + return self; +} + +static VALUE NIO_ByteBuffer_get(VALUE self) +{ + struct NIO_ByteBuffer *byteBuffer; + Data_Get_Struct(self, struct NIO_ByteBuffer, byteBuffer); + + int remaining = NUM2INT(NIO_ByteBuffer_remaining(self)); + + if(remaining == 0) { + return rb_str_new2(""); + } + + char tempArray[remaining+1]; + int i = 0; + + for(byteBuffer->position; byteBuffer->position <= byteBuffer->limit; byteBuffer->position++) { + tempArray[i++] = byteBuffer->buffer[byteBuffer->position]; + } + + tempArray[remaining] = '\0'; + return rb_str_new2(tempArray); +} + +static VALUE NIO_ByteBuffer_readnext(VALUE self, VALUE amount) +{ + int amnt = NUM2INT(amount); + if(amnt < 1) { + rb_raise(rb_eTypeError, "not a valid input"); + } + + if(amnt > NUM2INT(NIO_ByteBuffer_remaining(self))) { + rb_raise(rb_eTypeError, "Less number of elements remaining"); + } + + char tempArray[amnt+1]; + int c = 0; + struct NIO_ByteBuffer *byteBuffer; + Data_Get_Struct(self, struct NIO_ByteBuffer, byteBuffer); + + while(c < amnt) { + tempArray[c++] = byteBuffer->buffer[byteBuffer->position]; + byteBuffer->position++; + } + + tempArray[amnt] = '\0'; + return rb_str_new2(tempArray); +} + +static VALUE NIO_ByteBuffer_writeTo(VALUE self, VALUE io) +{ + struct NIO_ByteBuffer *byteBuffer; + Data_Get_Struct(self, struct NIO_ByteBuffer, byteBuffer); + int size = byteBuffer->limit + 1 - byteBuffer->position; + + #if HAVE_RB_IO_T + rb_io_t *fptr; + #else + OpenFile *fptr; + #endif + + GetOpenFile(rb_convert_type(io, T_FILE, "IO", "to_io"), fptr); + rb_io_set_nonblock(fptr); + + VALUE content = NIO_ByteBuffer_get(self); + char* contentAsPointer = StringValuePtr(content); + + write(FPTR_TO_FD(fptr), contentAsPointer , size); + + return self; +} + +static VALUE NIO_ByteBuffer_readFrom(VALUE self, VALUE io) +{ + struct NIO_ByteBuffer *byteBuffer; + Data_Get_Struct(self, struct NIO_ByteBuffer, byteBuffer); + int size = byteBuffer->limit + 1 - byteBuffer->position; + + #if HAVE_RB_IO_T + rb_io_t *fptr; + #else + OpenFile *fptr; + #endif + + GetOpenFile(rb_convert_type(io, T_FILE, "IO", "to_io"), fptr); + rb_io_set_nonblock(fptr); + + while(NIO_ByteBuffer_hasRemaining(self) == Qtrue) { + char* nextByte; + read(FPTR_TO_FD(fptr), &nextByte, 1); + VALUE byte = rb_str_new2(nextByte); + NIO_ByteBuffer_put(self, byte); + } + + return self; +} + +static VALUE NIO_ByteBuffer_remaining(VALUE self) +{ + struct NIO_ByteBuffer *byteBuffer; + Data_Get_Struct(self, struct NIO_ByteBuffer, byteBuffer); + + return INT2NUM(byteBuffer->limit + 1 - byteBuffer->position); +} + +static VALUE NIO_ByteBuffer_hasRemaining(VALUE self) +{ + struct NIO_ByteBuffer *byteBuffer; + Data_Get_Struct(self, struct NIO_ByteBuffer, byteBuffer); + + return ((byteBuffer->limit + 1 - byteBuffer->position) > 0) ? Qtrue : Qfalse; +} + +static VALUE NIO_ByteBuffer_getOffset(VALUE self) +{ + struct NIO_ByteBuffer *byteBuffer; + Data_Get_Struct(self, struct NIO_ByteBuffer, byteBuffer); + return INT2NUM(byteBuffer->offset); +} + +static VALUE NIO_ByteBuffer_equals(VALUE self, VALUE other) +{ + return self == other ? Qtrue : Qfalse; +} + +static VALUE NIO_ByteBuffer_flip(VALUE self) +{ + struct NIO_ByteBuffer *byteBuffer; + Data_Get_Struct(self, struct NIO_ByteBuffer, byteBuffer); + + byteBuffer->limit = (byteBuffer->position > 0) ? byteBuffer->position - 1 : 0; + byteBuffer->position = 0; + byteBuffer->mark = -1; + + return self; +} + +static VALUE NIO_ByteBuffer_rewind(VALUE self) +{ + struct NIO_ByteBuffer *byteBuffer; + Data_Get_Struct(self, struct NIO_ByteBuffer, byteBuffer); + + byteBuffer->position = 0; + byteBuffer->mark = -1; + + return self; +} + +static VALUE NIO_ByteBuffer_reset(VALUE self) +{ + struct NIO_ByteBuffer *byteBuffer; + Data_Get_Struct(self, struct NIO_ByteBuffer, byteBuffer); + + if(byteBuffer->mark < 0){ + rb_raise(rb_eRuntimeError, "Invalid Mark Exception"); + } else { + byteBuffer->position = byteBuffer->mark; + } + + return self; +} + +static VALUE NIO_ByteBuffer_markBuffer(VALUE self) +{ + struct NIO_ByteBuffer *byteBuffer; + Data_Get_Struct(self, struct NIO_ByteBuffer, byteBuffer); + + byteBuffer->mark = byteBuffer->position; + return self; +} + +static VALUE NIO_ByteBuffer_clear(VALUE self) +{ + struct NIO_ByteBuffer *byteBuffer; + Data_Get_Struct(self, struct NIO_ByteBuffer, byteBuffer); + + byteBuffer->position = 0; + byteBuffer->limit = byteBuffer->size - 1; + byteBuffer->mark = -1; + + return self; +} + +static VALUE NIO_ByteBuffer_compact(VALUE self) +{ + struct NIO_ByteBuffer *byteBuffer; + Data_Get_Struct(self, struct NIO_ByteBuffer, byteBuffer); + + if(NIO_ByteBuffer_hasRemaining(self) == Qtrue) { + //move content + int i = 0, j = byteBuffer->position; + for(j = byteBuffer->position; j <= byteBuffer->limit; j++) { + byteBuffer->buffer[i++] = byteBuffer->buffer[j]; + } + + byteBuffer->position = i; + byteBuffer->limit = byteBuffer->size - 1; + } + + return self; +} + +static VALUE NIO_ByteBuffer_capacity(VALUE self) +{ + struct NIO_ByteBuffer *byteBuffer; + Data_Get_Struct(self, struct NIO_ByteBuffer, byteBuffer); + + return INT2NUM(byteBuffer->size); +} + +static VALUE NIO_ByteBuffer_position(VALUE self, VALUE newposition) +{ + struct NIO_ByteBuffer *byteBuffer; + Data_Get_Struct(self, struct NIO_ByteBuffer, byteBuffer); + + int newPosition = NUM2INT(newposition); + + if(newPosition < 0 || newPosition > byteBuffer->limit) { + rb_raise(rb_eRuntimeError, "Illegal Argument Exception"); + } else { + byteBuffer->position = newPosition; + + if(byteBuffer->mark > newPosition) { + byteBuffer->mark = -1; + } + } + return self; +} + +static VALUE NIO_ByteBuffer_setLimit(VALUE self, VALUE newlimit) +{ + struct NIO_ByteBuffer *byteBuffer; + Data_Get_Struct(self, struct NIO_ByteBuffer, byteBuffer); + + int newLimit = NUM2INT(newlimit); + + if(newLimit < byteBuffer->size && newLimit >= 0) + { + byteBuffer->limit = newLimit; + + if(byteBuffer->position > byteBuffer->limit) { + byteBuffer->position = newLimit; + } + + if(byteBuffer->mark > byteBuffer->limit) { + byteBuffer->mark = -1; + } + } else { + rb_raise(rb_eRuntimeError, "Illegal Argument Exception"); + } + + return self; +} + +static VALUE NIO_ByteBuffer_getLimit(VALUE self) +{ + struct NIO_ByteBuffer *byteBuffer; + Data_Get_Struct(self, struct NIO_ByteBuffer, byteBuffer); + + return INT2NUM(byteBuffer->limit); +} + +static VALUE NIO_ByteBuffer_toString(VALUE self) +{ + struct NIO_ByteBuffer *byteBuffer; + Data_Get_Struct(self, struct NIO_ByteBuffer, byteBuffer); + + return rb_sprintf ("ByteBuffer [pos=%d lim=%d cap=%d]\n", byteBuffer->position, byteBuffer->limit, byteBuffer->size); +} diff --git a/ext/nio4r/nio4r.h b/ext/nio4r/nio4r.h index ea327f7..97615d3 100644 --- a/ext/nio4r/nio4r.h +++ b/ext/nio4r/nio4r.h @@ -41,6 +41,14 @@ struct NIO_Monitor struct NIO_Selector *selector; }; +struct NIO_ByteBuffer +{ + int size, offset, limit, position, mark; + char *buffer; + VALUE self; +}; + + #ifdef GetReadFile # define FPTR_TO_FD(fptr) (fileno(GetReadFile(fptr))) #else diff --git a/ext/nio4r/nio4r_ext.c b/ext/nio4r/nio4r_ext.c index 9e87209..bc8d114 100644 --- a/ext/nio4r/nio4r_ext.c +++ b/ext/nio4r/nio4r_ext.c @@ -8,9 +8,11 @@ void Init_NIO_Selector(); void Init_NIO_Monitor(); +void Init_NIO_ByteBuffer(); void Init_nio4r_ext() { Init_NIO_Selector(); Init_NIO_Monitor(); + Init_NIO_ByteBuffer(); } diff --git a/ext/nio4r/org/nio4r/ByteBuffer.java b/ext/nio4r/org/nio4r/ByteBuffer.java new file mode 100644 index 0000000..4c5e75b --- /dev/null +++ b/ext/nio4r/org/nio4r/ByteBuffer.java @@ -0,0 +1,283 @@ +package org.nio4r; + +import org.jruby.*; +import org.jruby.anno.JRubyMethod; +import org.jruby.javasupport.JavaUtil; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.nio.channels.FileChannel; +import java.util.ArrayList; + +/* +created by Upekshej + */ +public class ByteBuffer extends RubyObject { + + private java.nio.ByteBuffer byteBuffer; + private String currentWritePath = ""; + private String currentReadPath = ""; + + private FileChannel currentWriteFileChannel; + private FileOutputStream fileOutputStream; + + private FileInputStream currentReadChannel; + private FileChannel inChannel; + + public ByteBuffer(final Ruby ruby, RubyClass rubyClass) { + super(ruby, rubyClass); + } + + @JRubyMethod + public IRubyObject initialize(ThreadContext context, IRubyObject value, IRubyObject offset, IRubyObject length) { + Ruby ruby = context.getRuntime(); + if (value == ruby.getNil()) + throw new IllegalArgumentException(); + + if (value instanceof RubyString) { + if (offset != ruby.getNil() && length != ruby.getNil()) { + int arrayOffset = RubyNumeric.num2int(offset); + int arrayLimit = RubyNumeric.num2int(length); + byteBuffer = java.nio.ByteBuffer.wrap(value.asJavaString().getBytes(), arrayOffset, arrayLimit); + } else + byteBuffer = java.nio.ByteBuffer.wrap(value.asJavaString().getBytes()); + } else if (value instanceof RubyInteger) { + int allocationSize = RubyNumeric.num2int(value); + byteBuffer = java.nio.ByteBuffer.allocate(allocationSize); + } else { + throw new IllegalArgumentException(); + } + return this; + } + + /** + * Currently assuming only strings will come.. + * + * @param context + * @return + */ + @JRubyMethod(name = "<<") + public IRubyObject put(ThreadContext context, IRubyObject str) { + String string = str.asJavaString(); + + if (byteBuffer == null) + byteBuffer = java.nio.ByteBuffer.wrap(string.getBytes()); + byteBuffer.put(string.getBytes()); + return this; + } + + //https://www.ruby-forum.com/topic/3731325 + @JRubyMethod(name = "get") + public IRubyObject get(ThreadContext context) { + ArrayList temp = new ArrayList(); + while (byteBuffer.hasRemaining()) { + temp.add(byteBuffer.get()); + } + // String returnString = new String(toPrimitives(temp)); + + return JavaUtil.convertJavaToRuby(context.getRuntime(), new String(toPrimitives(temp))); + } + + @JRubyMethod(name = "read_next") + public IRubyObject readNext(ThreadContext context, IRubyObject count) { + int c = RubyNumeric.num2int(count); + if (c < 1) + throw new IllegalArgumentException(); + if (c <= byteBuffer.remaining()) { + ArrayList temp = new ArrayList(); + + while (c > 0) { + temp.add(byteBuffer.get()); + c = c - 1; + } + return JavaUtil.convertJavaToRuby(context.getRuntime(), new String(toPrimitives(temp))); + } + return JavaUtil.convertJavaToRuby(context.getRuntime(), ""); //Empty String + } + + private byte[] toPrimitives(ArrayList oBytes) { + byte[] bytes = new byte[oBytes.size()]; + for (int i = 0; i < oBytes.size(); i++) { + bytes[i] = (oBytes.get(i) == null) ? " ".getBytes()[0] : oBytes.get(i); + } + return bytes; + } + + @JRubyMethod(name = "write_to") + public IRubyObject writeTo(ThreadContext context, IRubyObject f) { + try { + File file = (File) JavaUtil.unwrapJavaObject(f); + if (!isTheSameFile(file, false)) { + currentWritePath = file.getAbsolutePath(); + if (currentWriteFileChannel != null) currentWriteFileChannel.close(); + if (fileOutputStream != null) fileOutputStream.close(); + + fileOutputStream = new FileOutputStream(file, true); + currentWriteFileChannel = fileOutputStream.getChannel(); + } + currentWriteFileChannel.write(byteBuffer); + } catch (Exception e) { + throw new IllegalArgumentException("File Write Operation Error: " + e.getLocalizedMessage()); + } + return this; + } + + @JRubyMethod(name = "read_from") + public IRubyObject readFrom(ThreadContext context, IRubyObject f) { + try { + File file = (File) JavaUtil.unwrapJavaObject(f); + if (!isTheSameFile(file, true)) { + inChannel.close(); + currentReadChannel.close(); + currentReadPath = file.getAbsolutePath(); + currentReadChannel = new FileInputStream(file); + inChannel = currentReadChannel.getChannel(); + } + inChannel.read(byteBuffer); + } catch (Exception e) { + throw new IllegalArgumentException("File Read Operation Error: " + e.getLocalizedMessage()); + } + return this; + } + + private boolean isTheSameFile(File f, boolean read) { + if (read) + return (currentReadPath == f.getAbsolutePath()); + return currentWritePath == f.getAbsolutePath(); + } + + @JRubyMethod(name = "remaining") + public IRubyObject remainingPositions(ThreadContext context) { + Ruby ruby = context.getRuntime(); + int count = byteBuffer.remaining(); + return RubyNumeric.int2fix(ruby, count); + } + + @JRubyMethod(name = "remaining?") + public IRubyObject hasRemaining(ThreadContext context) { + if (byteBuffer.hasRemaining()) + return context.getRuntime().getTrue(); + + return context.getRuntime().getFalse(); + } + + @JRubyMethod(name = "offset?") + public IRubyObject getOffset(ThreadContext context) { + int offset = byteBuffer.arrayOffset(); + return JavaUtil.convertJavaToRuby(context.getRuntime(), offset); + } + + /** + * Check whether the two ByteBuffers are the same. + * + * @param context + * @param ob : The RubyObject which needs to be check + * @return + */ + @JRubyMethod(name = "equals?") + public IRubyObject equals(ThreadContext context, IRubyObject ob) { + boolean equal = this.byteBuffer.equals(((ByteBuffer) JavaUtil.convertRubyToJava(ob)).getBuffer()); + if (equal) + context.getRuntime().getTrue(); + return context.getRuntime().getFalse(); + } + + /** + * Flip capability provided by the java nio.ByteBuffer + * buf.put(magic); // Prepend header + * in.read(buf); // Read data into rest of buffer + * buf.flip(); // Flip buffer + * out.write(buf); // Write header + data to channel + * + * @param context + * @return + */ + @JRubyMethod + public IRubyObject flip(ThreadContext context) { + byteBuffer.flip(); + return this; + } + + /** + * Rewinds the buffer. Usage in java is like + * out.write(buf); // Write remaining data + * buf.rewind(); // Rewind buffer + * buf.get(array); // Copy data into array + * + * @param context + * @return + */ + @JRubyMethod + public IRubyObject rewind(ThreadContext context) { + byteBuffer.rewind(); + return this; + } + + @JRubyMethod + public IRubyObject reset(ThreadContext context) { + byteBuffer.reset(); + return this; + } + + @JRubyMethod + public IRubyObject mark(ThreadContext context) { + byteBuffer.mark(); + return this; + } + + /** + * Removes all the content in the byteBuffer + * + * @param context + * @return + */ + @JRubyMethod + public IRubyObject clear(ThreadContext context) { + byteBuffer.clear(); + return this; + } + + @JRubyMethod + public IRubyObject compact(ThreadContext context) { + byteBuffer.compact(); + return this; + } + + @JRubyMethod(name = "capacity") + public IRubyObject capacity(ThreadContext context) { + int cap = byteBuffer.capacity(); + return JavaUtil.convertJavaToRuby(context.getRuntime(), cap); + } + + @JRubyMethod + public IRubyObject position(ThreadContext context, IRubyObject newPosition) { + int position = RubyNumeric.num2int(newPosition); + byteBuffer.position(position); + return this; + } + + @JRubyMethod(name = "limit") + public IRubyObject limit(ThreadContext context, IRubyObject newLimit) { + int limit = RubyNumeric.num2int(newLimit); + byteBuffer.limit(limit); + return this; + } + + @JRubyMethod(name = "limit?") + public IRubyObject limit(ThreadContext context) { + int lmt = byteBuffer.limit(); + return JavaUtil.convertJavaToRuby(context.getRuntime(), lmt); + } + + @JRubyMethod(name = "to_s") + public IRubyObject to_String(ThreadContext context) { + return JavaUtil.convertJavaToRuby(context.getRuntime(), byteBuffer.toString()); + } + + public java.nio.ByteBuffer getBuffer() { + return byteBuffer; + } +} \ No newline at end of file diff --git a/ext/nio4r/org/nio4r/Nio4r.java b/ext/nio4r/org/nio4r/Nio4r.java index c0b8716..a82d8e5 100644 --- a/ext/nio4r/org/nio4r/Nio4r.java +++ b/ext/nio4r/org/nio4r/Nio4r.java @@ -21,6 +21,7 @@ import org.jruby.runtime.load.Library; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.Block; +import org.nio4r.ByteBuffer; public class Nio4r implements Library { private Ruby ruby; @@ -45,6 +46,14 @@ public IRubyObject allocate(Ruby ruby, RubyClass rc) { }, nio); monitor.defineAnnotatedMethods(Monitor.class); + + RubyClass byteBuffer = ruby.defineClassUnder("ByteBuffer", ruby.getObject(), new ObjectAllocator() { + public IRubyObject allocate(Ruby ruby, RubyClass rc) { + return new ByteBuffer(ruby, rc); + } + }, nio); + + byteBuffer.defineAnnotatedMethods(ByteBuffer.class); } public static int symbolToInterestOps(Ruby ruby, SelectableChannel channel, IRubyObject interest) { diff --git a/lib/nio.rb b/lib/nio.rb index 3f88a20..3d1d0e2 100644 --- a/lib/nio.rb +++ b/lib/nio.rb @@ -18,6 +18,7 @@ def self.engine if ENV["NIO4R_PURE"] == "true" || (Gem.win_platform? && !defined?(JRUBY_VERSION)) require "nio/monitor" require "nio/selector" + require "nio/bytebuffer" NIO::ENGINE = "ruby".freeze else require "nio4r_ext" diff --git a/lib/nio/bytebuffer.rb b/lib/nio/bytebuffer.rb new file mode 100644 index 0000000..98f9c6d --- /dev/null +++ b/lib/nio/bytebuffer.rb @@ -0,0 +1,175 @@ +module NIO + # rubocop:disable ClassLength + class ByteBuffer + def initialize(value, offset = nil, length = nil) + # value can be either STRING or INTEGER + fail "Illegal Argument Exception" if value.nil? + @position = 0 + @mark = -1 + if value.is_a? Integer + @size = value + @byte_array = Array.new(value) + elsif value.is_a? String + @byte_array = str.bytes + @size = @byte_array.size + end + @limit = @size - 1 + unless offset.nil? + @offset = offset + @position = offset + unless length.nil? + fail "Illegal Argument Exception" if offset + length >= value + @limit = offset + length + end + end + end + + # put the provided string to the buffer + def <<(str) + temp_buffer = str.bytes + temp_buffer.each { |x| put_byte x } + end + + # return the remaining number positions to read/ write + def remaining + @limit + 1 - @position + end + + # has any space remaining + def remaining? + remaining > 0 + end + + # this method is private + def put_byte(byte) + fail "Buffer Overflowed" if @position == @size + @byte_array[@position] = byte + @position += 1 + end + + # write content in the buffer to file + # call flip before calling this + # after write operation to the + # buffer + def write_to(file) + @file_to_write = file unless @file_to_write.eql? file + file.write get if remaining? + end + + # Fill the byteBuffer with content of the file + def read_from(file) + @file_to_read = file unless @file_to_read.eql? file + while (s = file.read(1)) && remaining? + put_byte(s) + end + end + + # flip from write to read mode + def flip + # need to avoid @position being negative + @limit = [@position - 1, 0].max + @position = 0 + @mark = -1 + end + + # rewind read mode to write mode. limit stays unchanged + def rewind + @position = 0 + @mark = -1 + end + + # reset the position to the previously marked position + def reset + fail "Invalid Mark Exception" if @mark < 0 + @position = @mark + self + end + + # mark the current position in order to reset later + def mark + @mark = @position + end + + # the current values are considered junk + def clear + @position = 0 + @limit = @size - 1 + @mark = -1 + self + end + + def compact + # compact should be allowed only if there are content remaining in the buffer + return self unless remaining? + temp = @byte_array.slice(@position, @limit) + # if 1 remaining the replaced range should be @byte_array[0..0] + @byte_array[0..remaining - 1] = temp + @position = remaining + @limit = @size - 1 + self + end + + # get the content of the byteBuffer. need to call rewind before calling get. + # return as a String + def get + return "" if @limit == 0 + temp = @byte_array[@position..@limit].pack("c*") + # next position to be read. it should be always less than or equal to size-1 + @position = [@limit + 1, @size].min + temp + end + + def read_next(count) + fail "Illegal Argument" unless count > 0 + fail "Less number of elements remaining" if count > remaining + temp = @byte_array[@position..@position + count - 1].pack("c*") + @position += count + temp + end + + # return the offset of the buffer + def offset? + @offset + end + + # check whether the obj is the same bytebuffer as this bytebuffer + def equals?(obj) + self == obj + end + + # returns the capacity of the buffer. This value is fixed to the initial size + def capacity + @size + end + + # Set the position to a different position + def position(new_position) + fail "Illegal Argument Exception" unless new_position <= @limit && new_position >= 0 + @position = new_position + @mark = -1 if @mark > @position + end + + def limit(new_limit) + fail "Illegal Argument Exception" if new_limit > @size || new_limit < 0 + @limit = new_limit + @position = @limit if @position > @limit + @mark = -1 if @mark > @limit + end + + def limit? + @limit + end + + def to_s + # convert String in byte form to the visible string + temp = "ByteBuffer " + temp += "[pos=" + @position.to_s + temp += " lim =" + @limit.to_s + temp += " cap=" + @size.to_s + temp += "]" + temp + end + + private :put_byte + end +end diff --git a/spec/nio/bytebuffer_spec.rb b/spec/nio/bytebuffer_spec.rb new file mode 100644 index 0000000..340ed26 --- /dev/null +++ b/spec/nio/bytebuffer_spec.rb @@ -0,0 +1,77 @@ +require "spec_helper" + +RSpec.describe NIO::ByteBuffer do + describe "#Behaviour of ByteBuffer" do + subject { bytebuffer } + + context "allocates a given size buffer" do + let(:bytebuffer) { NIO::ByteBuffer.new(256, nil, nil) } + + before :each do + bytebuffer.clear + end + + it "Checks the allocation" do + expect(bytebuffer.capacity).to eql(256) + end + + it "checks remaining" do + expect(bytebuffer.remaining).to eql(256) + end + + it "puts a given string to buffer" do + bytebuffer << "Song of Ice & Fire" + expect(bytebuffer.remaining).to eql(238) + end + + it "reads the content added" do + bytebuffer << "Test" + bytebuffer << "Text" + bytebuffer << "Dumb" + bytebuffer.flip + expect(bytebuffer.read_next(5)).to eql "TestT" + end + + it "rewinds the buffer" do + end + + it "compacts the buffer" do + bytebuffer << "Test" + bytebuffer << " Text" + bytebuffer << "Dumb" + bytebuffer.flip + bytebuffer.read_next 5 + bytebuffer.compact + bytebuffer << " RRMARTIN" + bytebuffer.flip + # expect(bytebuffer.limit?).to eql(10) + expect(bytebuffer.get).to eql("TextDumb RRMARTIN") + end + + it "flips the bytebuffer" do + bytebuffer << "Test" + bytebuffer.flip + expect(bytebuffer.get).to eql("Test") + end + + it "reads the next items" do + bytebuffer << "John Snow" + bytebuffer.flip + bytebuffer.read_next 5 + expect(bytebuffer.read_next(4)).to eql("Snow") + end + + it "clears the buffer" do + bytebuffer << "Game of Thrones" + bytebuffer.clear + expect(bytebuffer.remaining).to eql(256) + end + + it "gets the content of the bytebuffer" do + bytebuffer << "Test" + bytebuffer.flip + expect(bytebuffer.get).to eql("Test") + end + end + end +end