Skip to content

Commit

Permalink
Merge pull request #95 from celluloid/bytebuffers
Browse files Browse the repository at this point in the history
Implement ByteBuffers
  • Loading branch information
tarcieri authored Nov 5, 2016
2 parents f1f3f99 + df1a8fc commit 9d7cfca
Show file tree
Hide file tree
Showing 8 changed files with 969 additions and 0 deletions.
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();
}
284 changes: 284 additions & 0 deletions ext/nio4r/org/nio4r/ByteBuffer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
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 context.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 context.runtime.newRuntimeError("Invalid Arguiments Exception");
}
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()) {
org.jruby.util.ByteList temp = new ByteList(c);
while (c > 0) {
temp.append(byteBuffer.get());
c = c - 1;
}
return context.runtime.newString(temp);
}
return RubyString.newEmptyString(context.runtime); //Empty String
}

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) {
int count = byteBuffer.remaining();
return context.getRuntime().newFixnum(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 context.getRuntime().newFixnum(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) {
Object o = JavaUtil.convertRubyToJava(ob);
if(!(o instanceof ByteBuffer)) return context.getRuntime().getFalse();

boolean equal = this.byteBuffer.equals(((ByteBuffer) ).getBuffer());
if (equal)
return 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 context.getRuntime().newFixnum(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 context.getRuntime().newFixnum(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

0 comments on commit 9d7cfca

Please sign in to comment.