nio4r 1.2.1 → 2.0.0.pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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