Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ByteBuffers #95

Merged
merged 2 commits into from
Nov 5, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
413 changes: 413 additions & 0 deletions ext/nio4r/bytebuffer.c

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions ext/nio4r/nio4r.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions ext/nio4r/nio4r_ext.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
283 changes: 283 additions & 0 deletions ext/nio4r/org/nio4r/ByteBuffer.java
Original file line number Diff line number Diff line change
@@ -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();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you probably want to have the exception type consistent across implementations, currently it is a different kind (based on backend used) : this could be improved to do rb_raise(rb_eTypeError, "not a valid input") like in MRI : throw new runtime.newTypeError("not a valid input")


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();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... also users will be surprised to get "native" Java exceptions (from Ruby)

}
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<Byte> temp = new ArrayList<Byte>();
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<Byte> temp = new ArrayList<Byte>();
Copy link

@kares kares Sep 12, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will be quite innefficient (due boxing), instead allocate a org.jruby.util.ByteList temp = new ByteList(c);


while (c > 0) {
temp.add(byteBuffer.get());
c = c - 1;
}
return JavaUtil.convertJavaToRuby(context.getRuntime(), new String(toPrimitives(temp)));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to avoid the Byte[] -> char[] -> byte[] conversions do context.runtime.newString((ByteList) temp)

}
return JavaUtil.convertJavaToRuby(context.getRuntime(), ""); //Empty String
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... think there's a RubyString.newEmptyString(context.runtime) or something similar

}

private byte[] toPrimitives(ArrayList<Byte> 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());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should equals? really fail if not a wrapped ByteBuffer object is passed (possibly with a ClassCastException) ?

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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with primitive int, long values one could do context.runtime.newFixnum(int) directly.
... its really a minor issue in this case but it will be a bit faster and avoids boxing.

}

@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;
}
}
9 changes: 9 additions & 0 deletions ext/nio4r/org/nio4r/Nio4r.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions lib/nio.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading