nio4r 1.2.1 → 2.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,311 @@
1
+ package org.nio4r;
2
+
3
+ import org.jruby.*;
4
+ import org.jruby.anno.JRubyMethod;
5
+ import org.jruby.javasupport.JavaUtil;
6
+ import org.jruby.runtime.ThreadContext;
7
+ import org.jruby.runtime.builtin.IRubyObject;
8
+
9
+ import java.io.File;
10
+ import java.io.FileInputStream;
11
+ import java.io.FileOutputStream;
12
+ import java.nio.channels.FileChannel;
13
+ import java.util.ArrayList;
14
+
15
+ /*
16
+ created by Upekshej
17
+ */
18
+ public class ByteBuffer extends RubyObject {
19
+
20
+ private java.nio.ByteBuffer byteBuffer;
21
+ private String currentWritePath = "";
22
+ private String currentReadPath = "";
23
+
24
+ private FileChannel currentWriteFileChannel;
25
+ private FileOutputStream fileOutputStream;
26
+
27
+ private FileInputStream currentReadChannel;
28
+ private FileChannel inChannel;
29
+
30
+ public ByteBuffer(final Ruby ruby, RubyClass rubyClass) {
31
+ super(ruby, rubyClass);
32
+ }
33
+
34
+ @JRubyMethod
35
+ public IRubyObject initialize(ThreadContext context, IRubyObject value, IRubyObject offset, IRubyObject length) {
36
+ Ruby ruby = context.getRuntime();
37
+
38
+ if (value == ruby.getNil()) {
39
+ throw ruby.newTypeError("expected String or Integer for value, got NilClass");
40
+ }
41
+
42
+ if (value instanceof RubyString) {
43
+ if (offset != ruby.getNil() && length != ruby.getNil()) {
44
+ int arrayOffset = RubyNumeric.num2int(offset);
45
+ int arrayLimit = RubyNumeric.num2int(length);
46
+ byteBuffer = java.nio.ByteBuffer.wrap(value.asJavaString().getBytes(), arrayOffset, arrayLimit);
47
+ } else {
48
+ byteBuffer = java.nio.ByteBuffer.wrap(value.asJavaString().getBytes());
49
+ }
50
+ } else if (value instanceof RubyInteger) {
51
+ int allocationSize = RubyNumeric.num2int(value);
52
+ byteBuffer = java.nio.ByteBuffer.allocate(allocationSize);
53
+ } else {
54
+ throw ruby.newTypeError("expected String or Integer for value");
55
+ }
56
+
57
+ return this;
58
+ }
59
+
60
+ /**
61
+ * Currently assuming only strings will come..
62
+ *
63
+ * @param context
64
+ * @return
65
+ */
66
+ @JRubyMethod(name = "<<")
67
+ public IRubyObject put(ThreadContext context, IRubyObject str) {
68
+ String string = str.asJavaString();
69
+
70
+ if (byteBuffer == null) {
71
+ byteBuffer = java.nio.ByteBuffer.wrap(string.getBytes());
72
+ }
73
+
74
+ byteBuffer.put(string.getBytes());
75
+ return this;
76
+ }
77
+
78
+ //https://www.ruby-forum.com/topic/3731325
79
+ @JRubyMethod(name = "get")
80
+ public IRubyObject get(ThreadContext context) {
81
+ ArrayList<Byte> temp = new ArrayList<Byte>();
82
+
83
+ while (byteBuffer.hasRemaining()) {
84
+ temp.add(byteBuffer.get());
85
+ }
86
+
87
+ return JavaUtil.convertJavaToRuby(context.getRuntime(), new String(toPrimitives(temp)));
88
+ }
89
+
90
+ @JRubyMethod(name = "read_next")
91
+ public IRubyObject readNext(ThreadContext context, IRubyObject count) {
92
+ int c = RubyNumeric.num2int(count);
93
+
94
+ if (c < 1) {
95
+ throw new IllegalArgumentException();
96
+ }
97
+
98
+ if (c <= byteBuffer.remaining()) {
99
+ org.jruby.util.ByteList temp = new org.jruby.util.ByteList(c);
100
+
101
+ while (c > 0) {
102
+ temp.append(byteBuffer.get());
103
+ c = c - 1;
104
+ }
105
+
106
+ return context.runtime.newString(temp);
107
+ }
108
+
109
+ return RubyString.newEmptyString(context.runtime);
110
+ }
111
+
112
+ private byte[] toPrimitives(ArrayList<Byte> oBytes) {
113
+ byte[] bytes = new byte[oBytes.size()];
114
+
115
+ for (int i = 0; i < oBytes.size(); i++) {
116
+ bytes[i] = (oBytes.get(i) == null) ? " ".getBytes()[0] : oBytes.get(i);
117
+ }
118
+
119
+ return bytes;
120
+ }
121
+
122
+ @JRubyMethod(name = "write_to")
123
+ public IRubyObject writeTo(ThreadContext context, IRubyObject f) {
124
+ try {
125
+ File file = (File) JavaUtil.unwrapJavaObject(f);
126
+
127
+ if (!isTheSameFile(file, false)) {
128
+ currentWritePath = file.getAbsolutePath();
129
+ if (currentWriteFileChannel != null) currentWriteFileChannel.close();
130
+ if (fileOutputStream != null) fileOutputStream.close();
131
+
132
+ fileOutputStream = new FileOutputStream(file, true);
133
+ currentWriteFileChannel = fileOutputStream.getChannel();
134
+ }
135
+
136
+ currentWriteFileChannel.write(byteBuffer);
137
+ } catch (Exception e) {
138
+ throw new IllegalArgumentException("write error: " + e.getLocalizedMessage());
139
+ }
140
+
141
+ return this;
142
+ }
143
+
144
+ @JRubyMethod(name = "read_from")
145
+ public IRubyObject readFrom(ThreadContext context, IRubyObject f) {
146
+ try {
147
+ File file = (File) JavaUtil.unwrapJavaObject(f);
148
+
149
+ if (!isTheSameFile(file, true)) {
150
+ inChannel.close();
151
+ currentReadChannel.close();
152
+ currentReadPath = file.getAbsolutePath();
153
+ currentReadChannel = new FileInputStream(file);
154
+ inChannel = currentReadChannel.getChannel();
155
+ }
156
+
157
+ inChannel.read(byteBuffer);
158
+ } catch (Exception e) {
159
+ throw new IllegalArgumentException("read error: " + e.getLocalizedMessage());
160
+ }
161
+
162
+ return this;
163
+ }
164
+
165
+ private boolean isTheSameFile(File f, boolean read) {
166
+ if (read) {
167
+ return (currentReadPath == f.getAbsolutePath());
168
+ }
169
+
170
+ return currentWritePath == f.getAbsolutePath();
171
+ }
172
+
173
+ @JRubyMethod(name = "remaining")
174
+ public IRubyObject remainingPositions(ThreadContext context) {
175
+ int count = byteBuffer.remaining();
176
+ return context.getRuntime().newFixnum(count);
177
+ }
178
+
179
+ @JRubyMethod(name = "remaining?")
180
+ public IRubyObject hasRemaining(ThreadContext context) {
181
+ if (byteBuffer.hasRemaining()) {
182
+ return context.getRuntime().getTrue();
183
+ }
184
+
185
+ return context.getRuntime().getFalse();
186
+ }
187
+
188
+ @JRubyMethod(name = "offset?")
189
+ public IRubyObject getOffset(ThreadContext context) {
190
+ int offset = byteBuffer.arrayOffset();
191
+ return context.getRuntime().newFixnum(offset);
192
+ }
193
+
194
+ /**
195
+ * Check whether the two ByteBuffers are the same.
196
+ *
197
+ * @param context
198
+ * @param ob : The RubyObject which needs to be check
199
+ * @return
200
+ */
201
+ @JRubyMethod(name = "equals?")
202
+ public IRubyObject equals(ThreadContext context, IRubyObject obj) {
203
+ Object o = JavaUtil.convertRubyToJava(obj);
204
+
205
+ if(!(o instanceof ByteBuffer)) {
206
+ return context.getRuntime().getFalse();
207
+ }
208
+
209
+ if(this.byteBuffer.equals(((ByteBuffer)o).getBuffer())) {
210
+ return context.getRuntime().getTrue();
211
+ } else {
212
+ return context.getRuntime().getFalse();
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Flip capability provided by the java nio.ByteBuffer
218
+ * buf.put(magic); // Prepend header
219
+ * in.read(buf); // Read data into rest of buffer
220
+ * buf.flip(); // Flip buffer
221
+ * out.write(buf); // Write header + data to channel
222
+ *
223
+ * @param context
224
+ * @return
225
+ */
226
+ @JRubyMethod
227
+ public IRubyObject flip(ThreadContext context) {
228
+ byteBuffer.flip();
229
+ return this;
230
+ }
231
+
232
+ /**
233
+ * Rewinds the buffer. Usage in java is like
234
+ * out.write(buf); // Write remaining data
235
+ * buf.rewind(); // Rewind buffer
236
+ * buf.get(array); // Copy data into array
237
+ *
238
+ * @param context
239
+ * @return
240
+ */
241
+ @JRubyMethod
242
+ public IRubyObject rewind(ThreadContext context) {
243
+ byteBuffer.rewind();
244
+ return this;
245
+ }
246
+
247
+ @JRubyMethod
248
+ public IRubyObject reset(ThreadContext context) {
249
+ byteBuffer.reset();
250
+ return this;
251
+ }
252
+
253
+ @JRubyMethod
254
+ public IRubyObject mark(ThreadContext context) {
255
+ byteBuffer.mark();
256
+ return this;
257
+ }
258
+
259
+ /**
260
+ * Removes all the content in the byteBuffer
261
+ *
262
+ * @param context
263
+ * @return
264
+ */
265
+ @JRubyMethod
266
+ public IRubyObject clear(ThreadContext context) {
267
+ byteBuffer.clear();
268
+ return this;
269
+ }
270
+
271
+ @JRubyMethod
272
+ public IRubyObject compact(ThreadContext context) {
273
+ byteBuffer.compact();
274
+ return this;
275
+ }
276
+
277
+ @JRubyMethod(name = "capacity")
278
+ public IRubyObject capacity(ThreadContext context) {
279
+ int cap = byteBuffer.capacity();
280
+ return context.getRuntime().newFixnum(cap);
281
+ }
282
+
283
+ @JRubyMethod
284
+ public IRubyObject position(ThreadContext context, IRubyObject newPosition) {
285
+ int position = RubyNumeric.num2int(newPosition);
286
+ byteBuffer.position(position);
287
+ return this;
288
+ }
289
+
290
+ @JRubyMethod(name = "limit")
291
+ public IRubyObject limit(ThreadContext context, IRubyObject newLimit) {
292
+ int limit = RubyNumeric.num2int(newLimit);
293
+ byteBuffer.limit(limit);
294
+ return this;
295
+ }
296
+
297
+ @JRubyMethod(name = "limit?")
298
+ public IRubyObject limit(ThreadContext context) {
299
+ int lmt = byteBuffer.limit();
300
+ return context.getRuntime().newFixnum(lmt);
301
+ }
302
+
303
+ @JRubyMethod(name = "to_s")
304
+ public IRubyObject to_String(ThreadContext context) {
305
+ return JavaUtil.convertJavaToRuby(context.getRuntime(), byteBuffer.toString());
306
+ }
307
+
308
+ public java.nio.ByteBuffer getBuffer() {
309
+ return byteBuffer;
310
+ }
311
+ }
@@ -21,6 +21,7 @@ import org.jruby.runtime.ThreadContext;
21
21
  import org.jruby.runtime.load.Library;
22
22
  import org.jruby.runtime.builtin.IRubyObject;
23
23
  import org.jruby.runtime.Block;
24
+ import org.nio4r.ByteBuffer;
24
25
 
25
26
  public class Nio4r implements Library {
26
27
  private Ruby ruby;
@@ -45,6 +46,14 @@ public class Nio4r implements Library {
45
46
  }, nio);
46
47
 
47
48
  monitor.defineAnnotatedMethods(Monitor.class);
49
+
50
+ RubyClass byteBuffer = ruby.defineClassUnder("ByteBuffer", ruby.getObject(), new ObjectAllocator() {
51
+ public IRubyObject allocate(Ruby ruby, RubyClass rc) {
52
+ return new ByteBuffer(ruby, rc);
53
+ }
54
+ }, nio);
55
+
56
+ byteBuffer.defineAnnotatedMethods(ByteBuffer.class);
48
57
  }
49
58
 
50
59
  public static int symbolToInterestOps(Ruby ruby, SelectableChannel channel, IRubyObject interest) {
data/ext/nio4r/selector.c CHANGED
@@ -93,7 +93,9 @@ static VALUE NIO_Selector_allocate(VALUE klass)
93
93
  rb_sys_fail("pipe");
94
94
  }
95
95
 
96
- if(fcntl(fds[0], F_SETFL, O_NONBLOCK) < 0) {
96
+ /* Use non-blocking reads/writes during wakeup, in case the buffer is full */
97
+ if(fcntl(fds[0], F_SETFL, O_NONBLOCK) < 0 ||
98
+ fcntl(fds[1], F_SETFL, O_NONBLOCK) < 0) {
97
99
  rb_sys_fail("fcntl");
98
100
  }
99
101
 
@@ -0,0 +1,175 @@
1
+ module NIO
2
+ # rubocop:disable ClassLength
3
+ class ByteBuffer
4
+ def initialize(value, offset = nil, length = nil)
5
+ # value can be either STRING or INTEGER
6
+ fail "not a valid input" if value.nil?
7
+ @position = 0
8
+ @mark = -1
9
+ if value.is_a? Integer
10
+ @size = value
11
+ @byte_array = Array.new(value)
12
+ elsif value.is_a? String
13
+ @byte_array = str.bytes
14
+ @size = @byte_array.size
15
+ end
16
+ @limit = @size - 1
17
+ unless offset.nil?
18
+ @offset = offset
19
+ @position = offset
20
+ unless length.nil?
21
+ fail "Invalid Arguiments Exception" if offset + length >= value
22
+ @limit = offset + length
23
+ end
24
+ end
25
+ end
26
+
27
+ # put the provided string to the buffer
28
+ def <<(str)
29
+ temp_buffer = str.bytes
30
+ temp_buffer.each { |x| put_byte x }
31
+ end
32
+
33
+ # return the remaining number positions to read/ write
34
+ def remaining
35
+ @limit + 1 - @position
36
+ end
37
+
38
+ # has any space remaining
39
+ def remaining?
40
+ remaining > 0
41
+ end
42
+
43
+ # this method is private
44
+ def put_byte(byte)
45
+ fail "Buffer Overflowed" if @position == @size
46
+ @byte_array[@position] = byte
47
+ @position += 1
48
+ end
49
+
50
+ # write content in the buffer to file
51
+ # call flip before calling this
52
+ # after write operation to the
53
+ # buffer
54
+ def write_to(file)
55
+ @file_to_write = file unless @file_to_write.eql? file
56
+ file.write get if remaining?
57
+ end
58
+
59
+ # Fill the byteBuffer with content of the file
60
+ def read_from(file)
61
+ @file_to_read = file unless @file_to_read.eql? file
62
+ while (s = file.read(1)) && remaining?
63
+ put_byte(s)
64
+ end
65
+ end
66
+
67
+ # flip from write to read mode
68
+ def flip
69
+ # need to avoid @position being negative
70
+ @limit = [@position - 1, 0].max
71
+ @position = 0
72
+ @mark = -1
73
+ end
74
+
75
+ # rewind read mode to write mode. limit stays unchanged
76
+ def rewind
77
+ @position = 0
78
+ @mark = -1
79
+ end
80
+
81
+ # reset the position to the previously marked position
82
+ def reset
83
+ fail "Invalid Mark Exception" if @mark < 0
84
+ @position = @mark
85
+ self
86
+ end
87
+
88
+ # mark the current position in order to reset later
89
+ def mark
90
+ @mark = @position
91
+ end
92
+
93
+ # the current values are considered junk
94
+ def clear
95
+ @position = 0
96
+ @limit = @size - 1
97
+ @mark = -1
98
+ self
99
+ end
100
+
101
+ def compact
102
+ # compact should be allowed only if there are content remaining in the buffer
103
+ return self unless remaining?
104
+ temp = @byte_array.slice(@position, @limit)
105
+ # if 1 remaining the replaced range should be @byte_array[0..0]
106
+ @byte_array[0..remaining - 1] = temp
107
+ @position = remaining
108
+ @limit = @size - 1
109
+ self
110
+ end
111
+
112
+ # get the content of the byteBuffer. need to call rewind before calling get.
113
+ # return as a String
114
+ def get
115
+ return "" if @limit == 0
116
+ temp = @byte_array[@position..@limit].pack("c*")
117
+ # next position to be read. it should be always less than or equal to size-1
118
+ @position = [@limit + 1, @size].min
119
+ temp
120
+ end
121
+
122
+ def read_next(count)
123
+ fail "Illegal Argument" unless count > 0
124
+ fail "Less number of elements remaining" if count > remaining
125
+ temp = @byte_array[@position..@position + count - 1].pack("c*")
126
+ @position += count
127
+ temp
128
+ end
129
+
130
+ # return the offset of the buffer
131
+ def offset?
132
+ @offset
133
+ end
134
+
135
+ # check whether the obj is the same bytebuffer as this bytebuffer
136
+ def equals?(obj)
137
+ self == obj
138
+ end
139
+
140
+ # returns the capacity of the buffer. This value is fixed to the initial size
141
+ def capacity
142
+ @size
143
+ end
144
+
145
+ # Set the position to a different position
146
+ def position(new_position)
147
+ fail "Illegal Argument Exception" unless new_position <= @limit && new_position >= 0
148
+ @position = new_position
149
+ @mark = -1 if @mark > @position
150
+ end
151
+
152
+ def limit(new_limit)
153
+ fail "Illegal Argument Exception" if new_limit > @size || new_limit < 0
154
+ @limit = new_limit
155
+ @position = @limit if @position > @limit
156
+ @mark = -1 if @mark > @limit
157
+ end
158
+
159
+ def limit?
160
+ @limit
161
+ end
162
+
163
+ def to_s
164
+ # convert String in byte form to the visible string
165
+ temp = "ByteBuffer "
166
+ temp += "[pos=" + @position.to_s
167
+ temp += " lim =" + @limit.to_s
168
+ temp += " cap=" + @size.to_s
169
+ temp += "]"
170
+ temp
171
+ end
172
+
173
+ private :put_byte
174
+ end
175
+ end
data/lib/nio/selector.rb CHANGED
@@ -48,6 +48,8 @@ module NIO
48
48
 
49
49
  # Select which monitors are ready
50
50
  def select(timeout = nil)
51
+ selected_monitors = Set.new
52
+
51
53
  @lock.synchronize do
52
54
  readers = [@wakeup]
53
55
  writers = []
@@ -61,8 +63,6 @@ module NIO
61
63
  ready_readers, ready_writers = Kernel.select readers, writers, [], timeout
62
64
  return unless ready_readers # timeout or wakeup
63
65
 
64
- selected_monitors = Set.new
65
-
66
66
  ready_readers.each do |io|
67
67
  if io == @wakeup
68
68
  # Clear all wakeup signals we've received by reading them
@@ -81,15 +81,15 @@ module NIO
81
81
  monitor.readiness = (monitor.readiness == :r) ? :rw : :w
82
82
  selected_monitors << monitor
83
83
  end
84
+ end
84
85
 
85
- if block_given?
86
- selected_monitors.each do |m|
87
- yield m
88
- end
89
- selected_monitors.size
90
- else
91
- selected_monitors
86
+ if block_given?
87
+ selected_monitors.each do |m|
88
+ yield m
92
89
  end
90
+ selected_monitors.size
91
+ else
92
+ selected_monitors
93
93
  end
94
94
  end
95
95
 
@@ -101,7 +101,16 @@ module NIO
101
101
  # level-triggered behavior.
102
102
  def wakeup
103
103
  # Send the selector a signal in the form of writing data to a pipe
104
- @waker.write "\0"
104
+ begin
105
+ @waker.write_nonblock "\0"
106
+ rescue IO::WaitWritable
107
+ # This indicates the wakeup pipe is full, which means the other thread
108
+ # has already received many wakeup calls, but not processed them yet.
109
+ # The other thread will completely drain this pipe when it wakes up,
110
+ # so it's ok to ignore this exception if it occurs: we know the other
111
+ # thread has already been signaled to wake up
112
+ end
113
+
105
114
  nil
106
115
  end
107
116
 
data/lib/nio/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NIO
4
- VERSION = "1.2.1".freeze
4
+ VERSION = "2.0.0.pre".freeze
5
5
  end
data/lib/nio.rb CHANGED
@@ -18,6 +18,7 @@ end
18
18
  if ENV["NIO4R_PURE"] == "true" || (Gem.win_platform? && !defined?(JRUBY_VERSION))
19
19
  require "nio/monitor"
20
20
  require "nio/selector"
21
+ require "nio/bytebuffer"
21
22
  NIO::ENGINE = "ruby".freeze
22
23
  else
23
24
  require "nio4r_ext"
@@ -0,0 +1,77 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe NIO::ByteBuffer do
4
+ describe "#Behaviour of ByteBuffer" do
5
+ subject { bytebuffer }
6
+
7
+ context "allocates a given size buffer" do
8
+ let(:bytebuffer) { NIO::ByteBuffer.new(256, nil, nil) }
9
+
10
+ before :each do
11
+ bytebuffer.clear
12
+ end
13
+
14
+ it "Checks the allocation" do
15
+ expect(bytebuffer.capacity).to eql(256)
16
+ end
17
+
18
+ it "checks remaining" do
19
+ expect(bytebuffer.remaining).to eql(256)
20
+ end
21
+
22
+ it "puts a given string to buffer" do
23
+ bytebuffer << "Song of Ice & Fire"
24
+ expect(bytebuffer.remaining).to eql(238)
25
+ end
26
+
27
+ it "reads the content added" do
28
+ bytebuffer << "Test"
29
+ bytebuffer << "Text"
30
+ bytebuffer << "Dumb"
31
+ bytebuffer.flip
32
+ expect(bytebuffer.read_next(5)).to eql "TestT"
33
+ end
34
+
35
+ it "rewinds the buffer" do
36
+ end
37
+
38
+ it "compacts the buffer" do
39
+ bytebuffer << "Test"
40
+ bytebuffer << " Text"
41
+ bytebuffer << "Dumb"
42
+ bytebuffer.flip
43
+ bytebuffer.read_next 5
44
+ bytebuffer.compact
45
+ bytebuffer << " RRMARTIN"
46
+ bytebuffer.flip
47
+ # expect(bytebuffer.limit?).to eql(10)
48
+ expect(bytebuffer.get).to eql("TextDumb RRMARTIN")
49
+ end
50
+
51
+ it "flips the bytebuffer" do
52
+ bytebuffer << "Test"
53
+ bytebuffer.flip
54
+ expect(bytebuffer.get).to eql("Test")
55
+ end
56
+
57
+ it "reads the next items" do
58
+ bytebuffer << "John Snow"
59
+ bytebuffer.flip
60
+ bytebuffer.read_next 5
61
+ expect(bytebuffer.read_next(4)).to eql("Snow")
62
+ end
63
+
64
+ it "clears the buffer" do
65
+ bytebuffer << "Game of Thrones"
66
+ bytebuffer.clear
67
+ expect(bytebuffer.remaining).to eql(256)
68
+ end
69
+
70
+ it "gets the content of the bytebuffer" do
71
+ bytebuffer << "Test"
72
+ bytebuffer.flip
73
+ expect(bytebuffer.get).to eql("Test")
74
+ end
75
+ end
76
+ end
77
+ end