nio4r 2.0.0.pre-java → 2.1.0-java

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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.rubocop.yml +31 -38
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +9 -19
  6. data/CHANGES.md +94 -42
  7. data/Gemfile +11 -3
  8. data/Guardfile +10 -0
  9. data/LICENSE.txt +1 -1
  10. data/README.md +43 -136
  11. data/Rakefile +2 -0
  12. data/examples/echo_server.rb +1 -0
  13. data/ext/libev/Changes +9 -13
  14. data/ext/libev/ev.c +100 -74
  15. data/ext/libev/ev.h +4 -9
  16. data/ext/libev/ev_epoll.c +6 -3
  17. data/ext/libev/ev_kqueue.c +8 -4
  18. data/ext/libev/ev_poll.c +6 -3
  19. data/ext/libev/ev_port.c +8 -4
  20. data/ext/libev/ev_select.c +4 -2
  21. data/ext/nio4r/bytebuffer.c +265 -257
  22. data/ext/nio4r/extconf.rb +3 -9
  23. data/ext/nio4r/monitor.c +93 -46
  24. data/ext/nio4r/nio4r.h +6 -16
  25. data/ext/nio4r/org/nio4r/ByteBuffer.java +193 -209
  26. data/ext/nio4r/org/nio4r/Monitor.java +164 -0
  27. data/ext/nio4r/org/nio4r/Nio4r.java +13 -391
  28. data/ext/nio4r/org/nio4r/Selector.java +278 -0
  29. data/ext/nio4r/selector.c +72 -64
  30. data/lib/nio.rb +3 -3
  31. data/lib/nio/bytebuffer.rb +179 -132
  32. data/lib/nio/monitor.rb +64 -4
  33. data/lib/nio/selector.rb +36 -13
  34. data/lib/nio/version.rb +1 -1
  35. data/nio4r.gemspec +25 -19
  36. data/spec/nio/acceptables_spec.rb +6 -4
  37. data/spec/nio/bytebuffer_spec.rb +323 -51
  38. data/spec/nio/monitor_spec.rb +122 -79
  39. data/spec/nio/selectables/pipe_spec.rb +5 -1
  40. data/spec/nio/selectables/ssl_socket_spec.rb +15 -12
  41. data/spec/nio/selectables/tcp_socket_spec.rb +42 -31
  42. data/spec/nio/selectables/udp_socket_spec.rb +2 -0
  43. data/spec/nio/selector_spec.rb +10 -4
  44. data/spec/spec_helper.rb +24 -3
  45. data/spec/support/selectable_examples.rb +7 -5
  46. data/tasks/extension.rake +2 -0
  47. data/tasks/rspec.rake +2 -0
  48. data/tasks/rubocop.rake +2 -0
  49. metadata +18 -15
  50. data/.rubocop_todo.yml +0 -35
data/ext/nio4r/extconf.rb CHANGED
@@ -1,15 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "mkmf"
2
4
 
3
5
  have_header("unistd.h")
4
6
 
5
- if have_func("rb_thread_blocking_region")
6
- $defs << "-DHAVE_RB_THREAD_BLOCKING_REGION"
7
- end
8
-
9
- if have_func("rb_thread_call_without_gvl")
10
- $defs << "-DHAVE_RB_THEREAD_CALL_WITHOUT_GVL"
11
- end
12
-
13
7
  $defs << "-DEV_USE_SELECT" if have_header("sys/select.h")
14
8
 
15
9
  $defs << "-DEV_USE_POLL" if have_header("poll.h")
@@ -24,7 +18,7 @@ $defs << "-DEV_USE_PORT" if have_header("port.h")
24
18
 
25
19
  $defs << "-DHAVE_SYS_RESOURCE_H" if have_header("sys/resource.h")
26
20
 
27
- $defs << "-DHAVE_RUBYSIG_H" if RUBY_VERSION.to_f < 1.9
21
+ CONFIG["optflags"] << " -fno-strict-aliasing"
28
22
 
29
23
  dir_config "nio4r_ext"
30
24
  create_makefile "nio4r_ext"
data/ext/nio4r/monitor.c CHANGED
@@ -15,12 +15,13 @@ static void NIO_Monitor_free(struct NIO_Monitor *monitor);
15
15
 
16
16
  /* Methods */
17
17
  static VALUE NIO_Monitor_initialize(VALUE self, VALUE selector, VALUE io, VALUE interests);
18
- static VALUE NIO_Monitor_set_interests(VALUE self, VALUE interests);
19
-
20
18
  static VALUE NIO_Monitor_close(int argc, VALUE *argv, VALUE self);
21
19
  static VALUE NIO_Monitor_is_closed(VALUE self);
22
20
  static VALUE NIO_Monitor_io(VALUE self);
23
21
  static VALUE NIO_Monitor_interests(VALUE self);
22
+ static VALUE NIO_Monitor_set_interests(VALUE self, VALUE interests);
23
+ static VALUE NIO_Monitor_add_interest(VALUE self, VALUE interest);
24
+ static VALUE NIO_Monitor_remove_interest(VALUE self, VALUE interest);
24
25
  static VALUE NIO_Monitor_selector(VALUE self);
25
26
  static VALUE NIO_Monitor_is_readable(VALUE self);
26
27
  static VALUE NIO_Monitor_is_writable(VALUE self);
@@ -28,11 +29,9 @@ static VALUE NIO_Monitor_value(VALUE self);
28
29
  static VALUE NIO_Monitor_set_value(VALUE self, VALUE obj);
29
30
  static VALUE NIO_Monitor_readiness(VALUE self);
30
31
 
31
- #if HAVE_RB_IO_T
32
- rb_io_t *fptr;
33
- #else
34
- OpenFile *fptr;
35
- #endif
32
+ /* Internal C functions */
33
+ static int NIO_Monitor_symbol2interest(VALUE interests);
34
+ static void NIO_Monitor_update_interests(VALUE self, int interests);
36
35
 
37
36
  /* Monitor control how a channel is being waited for by a monitor */
38
37
  void Init_NIO_Monitor()
@@ -42,11 +41,13 @@ void Init_NIO_Monitor()
42
41
  rb_define_alloc_func(cNIO_Monitor, NIO_Monitor_allocate);
43
42
 
44
43
  rb_define_method(cNIO_Monitor, "initialize", NIO_Monitor_initialize, 3);
45
- rb_define_method(cNIO_Monitor, "interests=", NIO_Monitor_set_interests, 1);
46
44
  rb_define_method(cNIO_Monitor, "close", NIO_Monitor_close, -1);
47
45
  rb_define_method(cNIO_Monitor, "closed?", NIO_Monitor_is_closed, 0);
48
46
  rb_define_method(cNIO_Monitor, "io", NIO_Monitor_io, 0);
49
47
  rb_define_method(cNIO_Monitor, "interests", NIO_Monitor_interests, 0);
48
+ rb_define_method(cNIO_Monitor, "interests=", NIO_Monitor_set_interests, 1);
49
+ rb_define_method(cNIO_Monitor, "add_interest", NIO_Monitor_add_interest, 1);
50
+ rb_define_method(cNIO_Monitor, "remove_interest", NIO_Monitor_remove_interest, 1);
50
51
  rb_define_method(cNIO_Monitor, "selector", NIO_Monitor_selector, 0);
51
52
  rb_define_method(cNIO_Monitor, "value", NIO_Monitor_value, 0);
52
53
  rb_define_method(cNIO_Monitor, "value=", NIO_Monitor_set_value, 1);
@@ -77,12 +78,7 @@ static VALUE NIO_Monitor_initialize(VALUE self, VALUE io, VALUE interests, VALUE
77
78
  struct NIO_Monitor *monitor;
78
79
  struct NIO_Selector *selector;
79
80
  ID interests_id;
80
-
81
- #if HAVE_RB_IO_T
82
- rb_io_t *fptr;
83
- #else
84
- OpenFile *fptr;
85
- #endif
81
+ rb_io_t *fptr;
86
82
 
87
83
  interests_id = SYM2ID(interests);
88
84
 
@@ -120,38 +116,6 @@ static VALUE NIO_Monitor_initialize(VALUE self, VALUE io, VALUE interests, VALUE
120
116
  return Qnil;
121
117
  }
122
118
 
123
- static VALUE NIO_Monitor_set_interests(VALUE self, VALUE interests)
124
- {
125
- struct NIO_Monitor *monitor;
126
- ID interests_id;
127
-
128
- if(NIO_Monitor_is_closed(self) == Qtrue) {
129
- rb_raise(rb_eTypeError, "monitor is already closed");
130
- }
131
-
132
- interests_id = SYM2ID(interests);
133
- Data_Get_Struct(self, struct NIO_Monitor, monitor);
134
-
135
- if(interests_id == rb_intern("r")) {
136
- monitor->interests = EV_READ;
137
- } else if(interests_id == rb_intern("w")) {
138
- monitor->interests = EV_WRITE;
139
- } else if(interests_id == rb_intern("rw")) {
140
- monitor->interests = EV_READ | EV_WRITE;
141
- } else {
142
- rb_raise(rb_eArgError, "invalid interest type %s (must be :r, :w, or :rw)",
143
- RSTRING_PTR(rb_funcall(interests, rb_intern("inspect"), 0, 0)));
144
- }
145
-
146
- ev_io_stop(monitor->selector->ev_loop, &monitor->ev_io);
147
- ev_io_set(&monitor->ev_io, monitor->ev_io.fd, monitor->interests);
148
- ev_io_start(monitor->selector->ev_loop, &monitor->ev_io);
149
-
150
- rb_ivar_set(self, rb_intern("interests"), interests);
151
-
152
- return interests;
153
- }
154
-
155
119
  static VALUE NIO_Monitor_close(int argc, VALUE *argv, VALUE self)
156
120
  {
157
121
  VALUE deregister, selector;
@@ -197,6 +161,33 @@ static VALUE NIO_Monitor_interests(VALUE self)
197
161
  return rb_ivar_get(self, rb_intern("interests"));
198
162
  }
199
163
 
164
+ static VALUE NIO_Monitor_set_interests(VALUE self, VALUE interests)
165
+ {
166
+ NIO_Monitor_update_interests(self, NIO_Monitor_symbol2interest(interests));
167
+
168
+ return rb_ivar_get(self, rb_intern("interests"));
169
+ }
170
+
171
+ static VALUE NIO_Monitor_add_interest(VALUE self, VALUE interest) {
172
+ struct NIO_Monitor *monitor;
173
+ Data_Get_Struct(self, struct NIO_Monitor, monitor);
174
+
175
+ monitor->interests |= NIO_Monitor_symbol2interest(interest);
176
+ NIO_Monitor_update_interests(self, monitor->interests);
177
+
178
+ return rb_ivar_get(self, rb_intern("interests"));
179
+ }
180
+
181
+ static VALUE NIO_Monitor_remove_interest(VALUE self, VALUE interest) {
182
+ struct NIO_Monitor *monitor;
183
+ Data_Get_Struct(self, struct NIO_Monitor, monitor);
184
+
185
+ monitor->interests &= ~NIO_Monitor_symbol2interest(interest);
186
+ NIO_Monitor_update_interests(self, monitor->interests);
187
+
188
+ return rb_ivar_get(self, rb_intern("interests"));
189
+ }
190
+
200
191
  static VALUE NIO_Monitor_selector(VALUE self)
201
192
  {
202
193
  return rb_ivar_get(self, rb_intern("selector"));
@@ -251,3 +242,59 @@ static VALUE NIO_Monitor_is_writable(VALUE self)
251
242
  return Qfalse;
252
243
  }
253
244
  }
245
+
246
+ /* Internal C functions */
247
+
248
+ static int NIO_Monitor_symbol2interest(VALUE interests)
249
+ {
250
+ ID interests_id;
251
+ interests_id = SYM2ID(interests);
252
+
253
+ if(interests_id == rb_intern("r")) {
254
+ return EV_READ;
255
+ } else if(interests_id == rb_intern("w")) {
256
+ return EV_WRITE;
257
+ } else if(interests_id == rb_intern("rw")) {
258
+ return EV_READ | EV_WRITE;
259
+ } else {
260
+ rb_raise(rb_eArgError, "invalid interest type %s (must be :r, :w, or :rw)",
261
+ RSTRING_PTR(rb_funcall(interests, rb_intern("inspect"), 0, 0)));
262
+ }
263
+ }
264
+
265
+ static void NIO_Monitor_update_interests(VALUE self, int interests)
266
+ {
267
+ ID interests_id;
268
+ struct NIO_Monitor *monitor;
269
+ Data_Get_Struct(self, struct NIO_Monitor, monitor);
270
+
271
+ if(NIO_Monitor_is_closed(self) == Qtrue) {
272
+ rb_raise(rb_eEOFError, "monitor is closed");
273
+ }
274
+
275
+ if(interests) {
276
+ switch(interests) {
277
+ case EV_READ:
278
+ interests_id = rb_intern("r");
279
+ break;
280
+ case EV_WRITE:
281
+ interests_id = rb_intern("w");
282
+ break;
283
+ case EV_READ | EV_WRITE:
284
+ interests_id = rb_intern("rw");
285
+ break;
286
+ default:
287
+ rb_raise(rb_eRuntimeError, "bogus NIO_Monitor_update_interests! (%d)", interests);
288
+ }
289
+
290
+ rb_ivar_set(self, rb_intern("interests"), ID2SYM(interests_id));
291
+ } else {
292
+ rb_ivar_set(self, rb_intern("interests"), Qnil);
293
+ }
294
+
295
+ monitor->interests = interests;
296
+
297
+ ev_io_stop(monitor->selector->ev_loop, &monitor->ev_io);
298
+ ev_io_set(&monitor->ev_io, monitor->ev_io.fd, monitor->interests);
299
+ ev_io_start(monitor->selector->ev_loop, &monitor->ev_io);
300
+ }
data/ext/nio4r/nio4r.h CHANGED
@@ -7,22 +7,19 @@
7
7
  #define NIO4R_H
8
8
 
9
9
  #include "ruby.h"
10
- #if HAVE_RUBY_IO_H
11
- # include "ruby/io.h"
12
- #else
13
- # include "rubyio.h"
14
- #endif
10
+ #include "ruby/io.h"
15
11
  #include "libev.h"
16
12
 
17
13
  struct NIO_Selector
18
14
  {
19
- struct ev_loop *ev_loop;
15
+ ev_loop *ev_loop;
20
16
  struct ev_timer timer; /* for timeouts */
21
17
  struct ev_io wakeup;
22
18
 
23
- int wakeup_reader, wakeup_writer;
24
- int closed, selecting;
25
19
  int ready_count;
20
+ int closed, selecting;
21
+ int wakeup_reader, wakeup_writer;
22
+ volatile int wakeup_fired;
26
23
 
27
24
  VALUE ready_array;
28
25
  };
@@ -43,22 +40,15 @@ struct NIO_Monitor
43
40
 
44
41
  struct NIO_ByteBuffer
45
42
  {
46
- int size, offset, limit, position, mark;
47
43
  char *buffer;
48
- VALUE self;
44
+ int position, limit, capacity, mark;
49
45
  };
50
46
 
51
47
 
52
48
  #ifdef GetReadFile
53
49
  # define FPTR_TO_FD(fptr) (fileno(GetReadFile(fptr)))
54
50
  #else
55
-
56
- #if !HAVE_RB_IO_T || (RUBY_VERSION_MAJOR == 1 && RUBY_VERSION_MINOR == 8)
57
- # define FPTR_TO_FD(fptr) fileno(fptr->f)
58
- #else
59
51
  # define FPTR_TO_FD(fptr) fptr->fd
60
- #endif /* !HAVE_RB_IO_T */
61
-
62
52
  #endif /* GetReadFile */
63
53
 
64
54
  /* Thunk between libev callbacks in NIO::Monitors and NIO::Selectors */
@@ -1,311 +1,295 @@
1
1
  package org.nio4r;
2
2
 
3
- import org.jruby.*;
3
+ import java.io.IOException;
4
+ import java.nio.channels.Channel;
5
+ import java.nio.channels.SelectableChannel;
6
+ import java.nio.channels.ReadableByteChannel;
7
+ import java.nio.channels.WritableByteChannel;
8
+ import java.nio.BufferOverflowException;
9
+ import java.nio.BufferUnderflowException;
10
+ import java.nio.InvalidMarkException;
11
+
12
+ import org.jruby.Ruby;
13
+ import org.jruby.RubyClass;
14
+ import org.jruby.RubyIO;
15
+ import org.jruby.RubyNumeric;
16
+ import org.jruby.RubyObject;
17
+ import org.jruby.RubyString;
4
18
  import org.jruby.anno.JRubyMethod;
5
- import org.jruby.javasupport.JavaUtil;
19
+ import org.jruby.exceptions.RaiseException;
6
20
  import org.jruby.runtime.ThreadContext;
7
21
  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;
22
+ import org.jruby.runtime.Block;
14
23
 
15
24
  /*
16
25
  created by Upekshej
17
26
  */
18
27
  public class ByteBuffer extends RubyObject {
19
-
20
28
  private java.nio.ByteBuffer byteBuffer;
21
- private String currentWritePath = "";
22
- private String currentReadPath = "";
23
29
 
24
- private FileChannel currentWriteFileChannel;
25
- private FileOutputStream fileOutputStream;
30
+ public static RaiseException newOverflowError(ThreadContext context, String message) {
31
+ RubyClass klass = context.runtime.getModule("NIO").getClass("ByteBuffer").getClass("OverflowError");
32
+ return context.runtime.newRaiseException(klass, message);
33
+ }
26
34
 
27
- private FileInputStream currentReadChannel;
28
- private FileChannel inChannel;
35
+ public static RaiseException newUnderflowError(ThreadContext context, String message) {
36
+ RubyClass klass = context.runtime.getModule("NIO").getClass("ByteBuffer").getClass("UnderflowError");
37
+ return context.runtime.newRaiseException(klass, message);
38
+ }
39
+
40
+ public static RaiseException newMarkUnsetError(ThreadContext context, String message) {
41
+ RubyClass klass = context.runtime.getModule("NIO").getClass("ByteBuffer").getClass("MarkUnsetError");
42
+ return context.runtime.newRaiseException(klass, message);
43
+ }
29
44
 
30
45
  public ByteBuffer(final Ruby ruby, RubyClass rubyClass) {
31
46
  super(ruby, rubyClass);
32
47
  }
33
48
 
34
49
  @JRubyMethod
35
- public IRubyObject initialize(ThreadContext context, IRubyObject value, IRubyObject offset, IRubyObject length) {
36
- Ruby ruby = context.getRuntime();
50
+ public IRubyObject initialize(ThreadContext context, IRubyObject capacity) {
51
+ this.byteBuffer = java.nio.ByteBuffer.allocate(RubyNumeric.num2int(capacity));
52
+ return this;
53
+ }
54
+
55
+ @JRubyMethod
56
+ public IRubyObject clear(ThreadContext context) {
57
+ this.byteBuffer.clear();
58
+ return this;
59
+ }
37
60
 
38
- if (value == ruby.getNil()) {
39
- throw ruby.newTypeError("expected String or Integer for value, got NilClass");
61
+ @JRubyMethod(name = "position")
62
+ public IRubyObject getPosition(ThreadContext context) {
63
+ return context.getRuntime().newFixnum(this.byteBuffer.position());
64
+ }
65
+
66
+ @JRubyMethod(name = "position=")
67
+ public IRubyObject setPosition(ThreadContext context, IRubyObject newPosition) {
68
+ int pos = RubyNumeric.num2int(newPosition);
69
+
70
+ if(pos < 0) {
71
+ throw context.runtime.newArgumentError("negative position given");
40
72
  }
41
73
 
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");
74
+ if(pos > this.byteBuffer.limit()) {
75
+ throw context.runtime.newArgumentError("specified position exceeds limit");
55
76
  }
56
77
 
57
- return this;
78
+ try {
79
+ this.byteBuffer.position(pos);
80
+ return newPosition;
81
+ } catch(IllegalArgumentException e) {
82
+ throw context.runtime.newArgumentError(e.getLocalizedMessage());
83
+ }
58
84
  }
59
85
 
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();
86
+ @JRubyMethod(name = "limit")
87
+ public IRubyObject getLimit(ThreadContext context) {
88
+ return context.getRuntime().newFixnum(this.byteBuffer.limit());
89
+ }
69
90
 
70
- if (byteBuffer == null) {
71
- byteBuffer = java.nio.ByteBuffer.wrap(string.getBytes());
72
- }
91
+ @JRubyMethod(name = "limit=")
92
+ public IRubyObject setLimit(ThreadContext context, IRubyObject newLimit) {
93
+ int lim = RubyNumeric.num2int(newLimit);
73
94
 
74
- byteBuffer.put(string.getBytes());
75
- return this;
76
- }
95
+ if(lim < 0) {
96
+ throw context.runtime.newArgumentError("negative limit given");
97
+ }
77
98
 
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>();
99
+ if(lim > this.byteBuffer.capacity()) {
100
+ throw context.runtime.newArgumentError("specified limit exceeds capacity");
101
+ }
82
102
 
83
- while (byteBuffer.hasRemaining()) {
84
- temp.add(byteBuffer.get());
103
+ try {
104
+ this.byteBuffer.limit(lim);
105
+ return newLimit;
106
+ } catch(IllegalArgumentException e) {
107
+ throw context.runtime.newArgumentError(e.getLocalizedMessage());
85
108
  }
109
+ }
86
110
 
87
- return JavaUtil.convertJavaToRuby(context.getRuntime(), new String(toPrimitives(temp)));
111
+ @JRubyMethod(name = {"capacity", "size"})
112
+ public IRubyObject capacity(ThreadContext context) {
113
+ return context.getRuntime().newFixnum(this.byteBuffer.capacity());
88
114
  }
89
115
 
90
- @JRubyMethod(name = "read_next")
91
- public IRubyObject readNext(ThreadContext context, IRubyObject count) {
92
- int c = RubyNumeric.num2int(count);
116
+ @JRubyMethod
117
+ public IRubyObject remaining(ThreadContext context) {
118
+ return context.getRuntime().newFixnum(this.byteBuffer.remaining());
119
+ }
93
120
 
94
- if (c < 1) {
95
- throw new IllegalArgumentException();
121
+ @JRubyMethod(name = "full?")
122
+ public IRubyObject isFull(ThreadContext context) {
123
+ if (this.byteBuffer.hasRemaining()) {
124
+ return context.getRuntime().getFalse();
125
+ } else {
126
+ return context.getRuntime().getTrue();
96
127
  }
128
+ }
97
129
 
98
- if (c <= byteBuffer.remaining()) {
99
- org.jruby.util.ByteList temp = new org.jruby.util.ByteList(c);
130
+ @JRubyMethod
131
+ public IRubyObject get(ThreadContext context) {
132
+ return this.get(context, context.getRuntime().newFixnum(this.byteBuffer.remaining()));
133
+ }
100
134
 
101
- while (c > 0) {
102
- temp.append(byteBuffer.get());
103
- c = c - 1;
104
- }
135
+ @JRubyMethod
136
+ public IRubyObject get(ThreadContext context, IRubyObject length) {
137
+ int len = RubyNumeric.num2int(length);
138
+ byte[] bytes = new byte[len];
105
139
 
106
- return context.runtime.newString(temp);
140
+ try {
141
+ this.byteBuffer.get(bytes);
142
+ } catch(BufferUnderflowException e) {
143
+ throw ByteBuffer.newUnderflowError(context, "not enough data in buffer");
107
144
  }
108
145
 
109
- return RubyString.newEmptyString(context.runtime);
146
+ return RubyString.newString(context.getRuntime(), bytes);
110
147
  }
111
148
 
112
- private byte[] toPrimitives(ArrayList<Byte> oBytes) {
113
- byte[] bytes = new byte[oBytes.size()];
149
+ @JRubyMethod(name = "[]")
150
+ public IRubyObject fetch(ThreadContext context, IRubyObject index) {
151
+ int i = RubyNumeric.num2int(index);
114
152
 
115
- for (int i = 0; i < oBytes.size(); i++) {
116
- bytes[i] = (oBytes.get(i) == null) ? " ".getBytes()[0] : oBytes.get(i);
153
+ if(i < 0) {
154
+ throw context.runtime.newArgumentError("negative index given");
117
155
  }
118
156
 
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);
157
+ if(i >= this.byteBuffer.limit()) {
158
+ throw context.runtime.newArgumentError("index exceeds limit");
159
+ }
126
160
 
127
- if (!isTheSameFile(file, false)) {
128
- currentWritePath = file.getAbsolutePath();
129
- if (currentWriteFileChannel != null) currentWriteFileChannel.close();
130
- if (fileOutputStream != null) fileOutputStream.close();
161
+ return context.getRuntime().newFixnum(this.byteBuffer.get(i));
162
+ }
131
163
 
132
- fileOutputStream = new FileOutputStream(file, true);
133
- currentWriteFileChannel = fileOutputStream.getChannel();
134
- }
164
+ @JRubyMethod(name = "<<")
165
+ public IRubyObject put(ThreadContext context, IRubyObject str) {
166
+ String string = str.asJavaString();
135
167
 
136
- currentWriteFileChannel.write(byteBuffer);
137
- } catch (Exception e) {
138
- throw new IllegalArgumentException("write error: " + e.getLocalizedMessage());
168
+ try {
169
+ this.byteBuffer.put(string.getBytes());
170
+ } catch(BufferOverflowException e) {
171
+ throw ByteBuffer.newOverflowError(context, "buffer is full");
139
172
  }
140
173
 
141
174
  return this;
142
175
  }
143
176
 
144
177
  @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
- }
178
+ public IRubyObject readFrom(ThreadContext context, IRubyObject io) {
179
+ Ruby runtime = context.runtime;
180
+ Channel channel = RubyIO.convertToIO(context, io).getChannel();
156
181
 
157
- inChannel.read(byteBuffer);
158
- } catch (Exception e) {
159
- throw new IllegalArgumentException("read error: " + e.getLocalizedMessage());
182
+ if(!this.byteBuffer.hasRemaining()) {
183
+ throw ByteBuffer.newOverflowError(context, "buffer is full");
160
184
  }
161
185
 
162
- return this;
163
- }
186
+ if(!(channel instanceof ReadableByteChannel) || !(channel instanceof SelectableChannel)) {
187
+ throw runtime.newArgumentError("unsupported IO object: " + io.getType().toString());
188
+ }
164
189
 
165
- private boolean isTheSameFile(File f, boolean read) {
166
- if (read) {
167
- return (currentReadPath == f.getAbsolutePath());
190
+ try {
191
+ ((SelectableChannel)channel).configureBlocking(false);
192
+ } catch(IOException ie) {
193
+ throw runtime.newIOError(ie.getLocalizedMessage());
168
194
  }
169
195
 
170
- return currentWritePath == f.getAbsolutePath();
171
- }
196
+ try {
197
+ int bytesRead = ((ReadableByteChannel)channel).read(this.byteBuffer);
172
198
 
173
- @JRubyMethod(name = "remaining")
174
- public IRubyObject remainingPositions(ThreadContext context) {
175
- int count = byteBuffer.remaining();
176
- return context.getRuntime().newFixnum(count);
199
+ if(bytesRead >= 0) {
200
+ return runtime.newFixnum(bytesRead);
201
+ } else {
202
+ throw runtime.newEOFError();
203
+ }
204
+ } catch(IOException ie) {
205
+ throw runtime.newIOError(ie.getLocalizedMessage());
206
+ }
177
207
  }
178
208
 
179
- @JRubyMethod(name = "remaining?")
180
- public IRubyObject hasRemaining(ThreadContext context) {
181
- if (byteBuffer.hasRemaining()) {
182
- return context.getRuntime().getTrue();
183
- }
209
+ @JRubyMethod(name = "write_to")
210
+ public IRubyObject writeTo(ThreadContext context, IRubyObject io) {
211
+ Ruby runtime = context.runtime;
212
+ Channel channel = RubyIO.convertToIO(context, io).getChannel();
184
213
 
185
- return context.getRuntime().getFalse();
186
- }
214
+ if(!this.byteBuffer.hasRemaining()) {
215
+ throw ByteBuffer.newUnderflowError(context, "not enough data in buffer");
216
+ }
187
217
 
188
- @JRubyMethod(name = "offset?")
189
- public IRubyObject getOffset(ThreadContext context) {
190
- int offset = byteBuffer.arrayOffset();
191
- return context.getRuntime().newFixnum(offset);
192
- }
218
+ if(!(channel instanceof WritableByteChannel) || !(channel instanceof SelectableChannel)) {
219
+ throw runtime.newArgumentError("unsupported IO object: " + io.getType().toString());
220
+ }
193
221
 
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();
222
+ try {
223
+ ((SelectableChannel)channel).configureBlocking(false);
224
+ } catch(IOException ie) {
225
+ throw runtime.newIOError(ie.getLocalizedMessage());
207
226
  }
208
227
 
209
- if(this.byteBuffer.equals(((ByteBuffer)o).getBuffer())) {
210
- return context.getRuntime().getTrue();
211
- } else {
212
- return context.getRuntime().getFalse();
228
+ try {
229
+ int bytesWritten = ((WritableByteChannel)channel).write(this.byteBuffer);
230
+
231
+ if(bytesWritten >= 0) {
232
+ return runtime.newFixnum(bytesWritten);
233
+ } else {
234
+ throw runtime.newEOFError();
235
+ }
236
+ } catch(IOException ie) {
237
+ throw runtime.newIOError(ie.getLocalizedMessage());
213
238
  }
214
239
  }
215
240
 
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
241
  @JRubyMethod
227
242
  public IRubyObject flip(ThreadContext context) {
228
- byteBuffer.flip();
243
+ this.byteBuffer.flip();
229
244
  return this;
230
245
  }
231
246
 
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
247
  @JRubyMethod
242
248
  public IRubyObject rewind(ThreadContext context) {
243
- byteBuffer.rewind();
244
- return this;
245
- }
246
-
247
- @JRubyMethod
248
- public IRubyObject reset(ThreadContext context) {
249
- byteBuffer.reset();
249
+ this.byteBuffer.rewind();
250
250
  return this;
251
251
  }
252
252
 
253
253
  @JRubyMethod
254
254
  public IRubyObject mark(ThreadContext context) {
255
- byteBuffer.mark();
255
+ this.byteBuffer.mark();
256
256
  return this;
257
257
  }
258
258
 
259
- /**
260
- * Removes all the content in the byteBuffer
261
- *
262
- * @param context
263
- * @return
264
- */
265
259
  @JRubyMethod
266
- public IRubyObject clear(ThreadContext context) {
267
- byteBuffer.clear();
268
- return this;
260
+ public IRubyObject reset(ThreadContext context) {
261
+ try {
262
+ this.byteBuffer.reset();
263
+ return this;
264
+ } catch(InvalidMarkException ie) {
265
+ throw ByteBuffer.newMarkUnsetError(context, "mark has not been set");
266
+ }
269
267
  }
270
268
 
271
269
  @JRubyMethod
272
270
  public IRubyObject compact(ThreadContext context) {
273
- byteBuffer.compact();
271
+ this.byteBuffer.compact();
274
272
  return this;
275
273
  }
276
274
 
277
- @JRubyMethod(name = "capacity")
278
- public IRubyObject capacity(ThreadContext context) {
279
- int cap = byteBuffer.capacity();
280
- return context.getRuntime().newFixnum(cap);
281
- }
282
-
283
275
  @JRubyMethod
284
- public IRubyObject position(ThreadContext context, IRubyObject newPosition) {
285
- int position = RubyNumeric.num2int(newPosition);
286
- byteBuffer.position(position);
287
- return this;
288
- }
276
+ public IRubyObject each(ThreadContext context, Block block) {
277
+ for(int i = 0; i < this.byteBuffer.limit(); i++) {
278
+ block.call(context, context.getRuntime().newFixnum(this.byteBuffer.get(i)));
279
+ }
289
280
 
290
- @JRubyMethod(name = "limit")
291
- public IRubyObject limit(ThreadContext context, IRubyObject newLimit) {
292
- int limit = RubyNumeric.num2int(newLimit);
293
- byteBuffer.limit(limit);
294
281
  return this;
295
282
  }
296
283
 
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;
284
+ @JRubyMethod
285
+ public IRubyObject inspect(ThreadContext context) {
286
+ return context.runtime.newString(String.format(
287
+ "#<%s:0x%x @position=%d @limit=%d @capacity=%d>",
288
+ this.getType().toString(),
289
+ System.identityHashCode(this),
290
+ this.byteBuffer.position(),
291
+ this.byteBuffer.limit(),
292
+ this.byteBuffer.capacity()
293
+ ));
310
294
  }
311
295
  }