nio4r 2.0.0.pre → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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 +4 -21
  6. data/CHANGES.md +75 -42
  7. data/Gemfile +11 -3
  8. data/Guardfile +10 -0
  9. data/LICENSE.txt +1 -1
  10. data/README.md +32 -136
  11. data/Rakefile +2 -0
  12. data/examples/echo_server.rb +1 -0
  13. data/ext/libev/Changes +4 -13
  14. data/ext/libev/ev.c +100 -74
  15. data/ext/libev/ev.h +3 -3
  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 +2 -10
  23. data/ext/nio4r/monitor.c +93 -46
  24. data/ext/nio4r/nio4r.h +5 -15
  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 +52 -52
  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 +15 -11
  50. data/.rubocop_todo.yml +0 -35
@@ -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,8 +18,6 @@ $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
28
-
29
21
  dir_config "nio4r_ext"
30
22
  create_makefile "nio4r_ext"
31
23
 
@@ -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
+ }
@@ -7,11 +7,7 @@
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
@@ -20,9 +16,10 @@ struct NIO_Selector
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
  }