sonixlabs-eventmachine-java 1.0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (178) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.travis.yml +12 -0
  4. data/.yardopts +7 -0
  5. data/CHANGELOG.md +33 -0
  6. data/GNU +281 -0
  7. data/Gemfile +2 -0
  8. data/LICENSE +60 -0
  9. data/README.md +109 -0
  10. data/README_JP.md +18 -0
  11. data/Rakefile +20 -0
  12. data/docs/DocumentationGuidesIndex.md +27 -0
  13. data/docs/GettingStarted.md +521 -0
  14. data/docs/old/ChangeLog +211 -0
  15. data/docs/old/DEFERRABLES +246 -0
  16. data/docs/old/EPOLL +141 -0
  17. data/docs/old/INSTALL +13 -0
  18. data/docs/old/KEYBOARD +42 -0
  19. data/docs/old/LEGAL +25 -0
  20. data/docs/old/LIGHTWEIGHT_CONCURRENCY +130 -0
  21. data/docs/old/PURE_RUBY +75 -0
  22. data/docs/old/RELEASE_NOTES +94 -0
  23. data/docs/old/SMTP +4 -0
  24. data/docs/old/SPAWNED_PROCESSES +148 -0
  25. data/docs/old/TODO +8 -0
  26. data/eventmachine.gemspec +38 -0
  27. data/examples/guides/getting_started/01_eventmachine_echo_server.rb +18 -0
  28. data/examples/guides/getting_started/02_eventmachine_echo_server_that_recognizes_exit_command.rb +22 -0
  29. data/examples/guides/getting_started/03_simple_chat_server.rb +149 -0
  30. data/examples/guides/getting_started/04_simple_chat_server_step_one.rb +27 -0
  31. data/examples/guides/getting_started/05_simple_chat_server_step_two.rb +43 -0
  32. data/examples/guides/getting_started/06_simple_chat_server_step_three.rb +98 -0
  33. data/examples/guides/getting_started/07_simple_chat_server_step_four.rb +121 -0
  34. data/examples/guides/getting_started/08_simple_chat_server_step_five.rb +141 -0
  35. data/examples/old/ex_channel.rb +43 -0
  36. data/examples/old/ex_queue.rb +2 -0
  37. data/examples/old/ex_tick_loop_array.rb +15 -0
  38. data/examples/old/ex_tick_loop_counter.rb +32 -0
  39. data/examples/old/helper.rb +2 -0
  40. data/ext/binder.cpp +124 -0
  41. data/ext/binder.h +46 -0
  42. data/ext/cmain.cpp +887 -0
  43. data/ext/ed.cpp +1988 -0
  44. data/ext/ed.h +422 -0
  45. data/ext/em.cpp +2351 -0
  46. data/ext/em.h +244 -0
  47. data/ext/eventmachine.h +128 -0
  48. data/ext/extconf.rb +177 -0
  49. data/ext/fastfilereader/extconf.rb +103 -0
  50. data/ext/fastfilereader/mapper.cpp +214 -0
  51. data/ext/fastfilereader/mapper.h +59 -0
  52. data/ext/fastfilereader/rubymain.cpp +127 -0
  53. data/ext/kb.cpp +79 -0
  54. data/ext/page.cpp +107 -0
  55. data/ext/page.h +51 -0
  56. data/ext/pipe.cpp +347 -0
  57. data/ext/project.h +156 -0
  58. data/ext/rubymain.cpp +1318 -0
  59. data/ext/ssl.cpp +468 -0
  60. data/ext/ssl.h +94 -0
  61. data/java/.classpath +6 -0
  62. data/java/.gitignore +1 -0
  63. data/java/.project +17 -0
  64. data/java/src/com/rubyeventmachine/DatagramPacket.java +13 -0
  65. data/java/src/com/rubyeventmachine/EmReactor.java +529 -0
  66. data/java/src/com/rubyeventmachine/EmReactorException.java +40 -0
  67. data/java/src/com/rubyeventmachine/EventCallback.java +7 -0
  68. data/java/src/com/rubyeventmachine/EventCode.java +26 -0
  69. data/java/src/com/rubyeventmachine/EventableChannel.java +130 -0
  70. data/java/src/com/rubyeventmachine/EventableDatagramChannel.java +180 -0
  71. data/java/src/com/rubyeventmachine/EventableSocketChannel.java +405 -0
  72. data/java/src/com/rubyeventmachine/SslBox.java +310 -0
  73. data/lib/em/buftok.rb +110 -0
  74. data/lib/em/callback.rb +58 -0
  75. data/lib/em/channel.rb +64 -0
  76. data/lib/em/completion.rb +304 -0
  77. data/lib/em/connection.rb +712 -0
  78. data/lib/em/deferrable.rb +210 -0
  79. data/lib/em/deferrable/pool.rb +2 -0
  80. data/lib/em/file_watch.rb +73 -0
  81. data/lib/em/future.rb +61 -0
  82. data/lib/em/iterator.rb +231 -0
  83. data/lib/em/messages.rb +66 -0
  84. data/lib/em/pool.rb +151 -0
  85. data/lib/em/process_watch.rb +45 -0
  86. data/lib/em/processes.rb +123 -0
  87. data/lib/em/protocols.rb +37 -0
  88. data/lib/em/protocols/header_and_content.rb +138 -0
  89. data/lib/em/protocols/httpclient.rb +279 -0
  90. data/lib/em/protocols/httpclient2.rb +600 -0
  91. data/lib/em/protocols/line_and_text.rb +125 -0
  92. data/lib/em/protocols/line_protocol.rb +29 -0
  93. data/lib/em/protocols/linetext2.rb +161 -0
  94. data/lib/em/protocols/memcache.rb +331 -0
  95. data/lib/em/protocols/object_protocol.rb +46 -0
  96. data/lib/em/protocols/postgres3.rb +246 -0
  97. data/lib/em/protocols/saslauth.rb +175 -0
  98. data/lib/em/protocols/smtpclient.rb +365 -0
  99. data/lib/em/protocols/smtpserver.rb +643 -0
  100. data/lib/em/protocols/socks4.rb +66 -0
  101. data/lib/em/protocols/stomp.rb +205 -0
  102. data/lib/em/protocols/tcptest.rb +54 -0
  103. data/lib/em/pure_ruby.rb +1017 -0
  104. data/lib/em/queue.rb +71 -0
  105. data/lib/em/resolver.rb +192 -0
  106. data/lib/em/spawnable.rb +84 -0
  107. data/lib/em/streamer.rb +118 -0
  108. data/lib/em/threaded_resource.rb +90 -0
  109. data/lib/em/tick_loop.rb +85 -0
  110. data/lib/em/timers.rb +61 -0
  111. data/lib/em/version.rb +3 -0
  112. data/lib/eventmachine.rb +1553 -0
  113. data/lib/jeventmachine.rb +331 -0
  114. data/lib/sonixlabs-eventmachine-java.rb +1 -0
  115. data/rakelib/cpp.rake_example +77 -0
  116. data/rakelib/package.rake +96 -0
  117. data/rakelib/test.rake +8 -0
  118. data/tests/client.crt +31 -0
  119. data/tests/client.key +51 -0
  120. data/tests/em_test_helper.rb +64 -0
  121. data/tests/server.crt +36 -0
  122. data/tests/server.key +51 -0
  123. data/tests/test_attach.rb +150 -0
  124. data/tests/test_basic.rb +294 -0
  125. data/tests/test_channel.rb +62 -0
  126. data/tests/test_completion.rb +177 -0
  127. data/tests/test_connection_count.rb +53 -0
  128. data/tests/test_defer.rb +18 -0
  129. data/tests/test_deferrable.rb +35 -0
  130. data/tests/test_epoll.rb +145 -0
  131. data/tests/test_error_handler.rb +38 -0
  132. data/tests/test_exc.rb +28 -0
  133. data/tests/test_file_watch.rb +65 -0
  134. data/tests/test_futures.rb +170 -0
  135. data/tests/test_get_sock_opt.rb +37 -0
  136. data/tests/test_handler_check.rb +35 -0
  137. data/tests/test_hc.rb +155 -0
  138. data/tests/test_httpclient.rb +190 -0
  139. data/tests/test_httpclient2.rb +133 -0
  140. data/tests/test_idle_connection.rb +25 -0
  141. data/tests/test_inactivity_timeout.rb +54 -0
  142. data/tests/test_iterator.rb +97 -0
  143. data/tests/test_kb.rb +34 -0
  144. data/tests/test_line_protocol.rb +33 -0
  145. data/tests/test_ltp.rb +138 -0
  146. data/tests/test_ltp2.rb +288 -0
  147. data/tests/test_next_tick.rb +104 -0
  148. data/tests/test_object_protocol.rb +36 -0
  149. data/tests/test_pause.rb +102 -0
  150. data/tests/test_pending_connect_timeout.rb +52 -0
  151. data/tests/test_pool.rb +194 -0
  152. data/tests/test_process_watch.rb +48 -0
  153. data/tests/test_processes.rb +128 -0
  154. data/tests/test_proxy_connection.rb +180 -0
  155. data/tests/test_pure.rb +88 -0
  156. data/tests/test_queue.rb +50 -0
  157. data/tests/test_resolver.rb +55 -0
  158. data/tests/test_running.rb +14 -0
  159. data/tests/test_sasl.rb +47 -0
  160. data/tests/test_send_file.rb +217 -0
  161. data/tests/test_servers.rb +33 -0
  162. data/tests/test_set_sock_opt.rb +37 -0
  163. data/tests/test_shutdown_hooks.rb +23 -0
  164. data/tests/test_smtpclient.rb +55 -0
  165. data/tests/test_smtpserver.rb +57 -0
  166. data/tests/test_spawn.rb +293 -0
  167. data/tests/test_ssl_args.rb +78 -0
  168. data/tests/test_ssl_echo_data.rb +60 -0
  169. data/tests/test_ssl_methods.rb +56 -0
  170. data/tests/test_ssl_verify.rb +82 -0
  171. data/tests/test_stomp.rb +37 -0
  172. data/tests/test_system.rb +42 -0
  173. data/tests/test_threaded_resource.rb +53 -0
  174. data/tests/test_tick_loop.rb +59 -0
  175. data/tests/test_timers.rb +123 -0
  176. data/tests/test_ud.rb +8 -0
  177. data/tests/test_unbind_reason.rb +48 -0
  178. metadata +298 -0
@@ -0,0 +1,310 @@
1
+ package com.rubyeventmachine;
2
+
3
+ import java.io.IOException;
4
+ import java.nio.ByteBuffer;
5
+ import java.nio.channels.SelectionKey;
6
+ import java.nio.channels.SocketChannel;
7
+ import java.security.KeyManagementException;
8
+ import java.security.KeyStore;
9
+ import java.security.KeyStoreException;
10
+ import java.security.NoSuchAlgorithmException;
11
+ import java.security.UnrecoverableKeyException;
12
+
13
+ import javax.net.ssl.KeyManager;
14
+ import javax.net.ssl.KeyManagerFactory;
15
+ import javax.net.ssl.SSLContext;
16
+ import javax.net.ssl.SSLEngine;
17
+ import javax.net.ssl.SSLEngineResult;
18
+ import javax.net.ssl.SSLEngineResult.HandshakeStatus;
19
+ import javax.net.ssl.SSLEngineResult.Status;
20
+ import javax.net.ssl.SSLPeerUnverifiedException;
21
+ import javax.net.ssl.TrustManager;
22
+ import javax.net.ssl.X509TrustManager;
23
+
24
+ public class SslBox {
25
+
26
+ private final SSLContext sslContext;
27
+ private final SSLEngine sslEngine;
28
+
29
+ private final ByteBuffer netInBuffer;
30
+ private final ByteBuffer netOutBuffer;
31
+ private final ByteBuffer anotherBuffer;
32
+
33
+ public static ByteBuffer emptyBuf = ByteBuffer.allocate(0);
34
+ private final SocketChannel channel;
35
+
36
+ private boolean handshakeComplete;
37
+ protected HandshakeStatus handshakeStatus; //gets set by handshake
38
+
39
+ public SslBox(boolean isServer, SocketChannel channel, KeyStore keyStore, X509TrustManager tm, boolean verifyPeer, String host, int port) {
40
+ try {
41
+ sslContext = SSLContext.getInstance("TLS");
42
+ KeyManager[] keyManagers = null;
43
+
44
+ if (keyStore != null) {
45
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
46
+ kmf.init(keyStore, null);
47
+ keyManagers = kmf.getKeyManagers();
48
+ }
49
+
50
+ sslContext.init(keyManagers, new TrustManager[] { tm }, null);
51
+ sslEngine = sslContext.createSSLEngine(host, port);
52
+ sslEngine.setUseClientMode(!isServer);
53
+ sslEngine.setNeedClientAuth(verifyPeer);
54
+
55
+ this.channel = channel;
56
+
57
+ int netBufSize = sslEngine.getSession().getPacketBufferSize();
58
+ netInBuffer = ByteBuffer.allocate(netBufSize);
59
+ netOutBuffer = ByteBuffer.allocate(netBufSize);
60
+ anotherBuffer = ByteBuffer.allocate(netBufSize);
61
+ reset();
62
+ } catch (NoSuchAlgorithmException e) {
63
+ throw new RuntimeException("unable to start TLS: " + e.getMessage(), e);
64
+ } catch (UnrecoverableKeyException e) {
65
+ throw new RuntimeException("unable to start TLS: " + e.getMessage(), e);
66
+ } catch (KeyStoreException e) {
67
+ throw new RuntimeException("unable to start TLS: " + e.getMessage(), e);
68
+ } catch (KeyManagementException e) {
69
+ throw new RuntimeException("unable to start TLS: " + e.getMessage(), e);
70
+ } catch (IOException e) {
71
+ throw new RuntimeException("unable to start TLS: " + e.getMessage(), e);
72
+ }
73
+ }
74
+
75
+ public void reset() throws IOException {
76
+ netOutBuffer.position(0);
77
+ netOutBuffer.limit(0);
78
+ netInBuffer.position(0);
79
+ netInBuffer.limit(0);
80
+ handshakeComplete = false;
81
+ //initiate handshake
82
+ sslEngine.beginHandshake();
83
+ handshakeStatus = sslEngine.getHandshakeStatus();
84
+ }
85
+
86
+ public boolean handshake(SelectionKey channelKey) {
87
+ try {
88
+ int newOps = do_handshake(channelKey.isReadable(), channelKey.isWritable());
89
+ channelKey.interestOps(newOps);
90
+ } catch (IOException e) {
91
+ return false;
92
+ }
93
+ return true;
94
+ }
95
+
96
+ public boolean handshakeNeeded() {
97
+ return !handshakeComplete;
98
+ }
99
+
100
+ private int do_handshake(boolean read, boolean write) throws IOException {
101
+ if (!flush(netOutBuffer)) return SelectionKey.OP_WRITE; //we still have data to write
102
+
103
+ SSLEngineResult handshake = null;
104
+
105
+ while (!handshakeComplete) {
106
+ switch ( handshakeStatus ) {
107
+ case FINISHED: {
108
+ //we are complete if we have delivered the last package
109
+ handshakeComplete = !netOutBuffer.hasRemaining();
110
+ //return 0 if we are complete, otherwise we still have data to write
111
+ return handshakeComplete?0:SelectionKey.OP_WRITE;
112
+ }
113
+ case NEED_WRAP: {
114
+ //perform the wrap function
115
+ handshake = handshakeWrap(write);
116
+ if ( handshake.getStatus() == Status.OK ){
117
+ if (handshakeStatus == HandshakeStatus.NEED_TASK)
118
+ handshakeStatus = tasks();
119
+ } else {
120
+ //wrap should always work with our buffers
121
+ throw new IOException("Unexpected status:" + handshake.getStatus() + " during handshake WRAP.");
122
+ }
123
+ if ( handshakeStatus != HandshakeStatus.NEED_UNWRAP || (!flush(netOutBuffer)) ) {
124
+ //should actually return OP_READ if we have NEED_UNWRAP
125
+ return SelectionKey.OP_WRITE;
126
+ }
127
+ //fall down to NEED_UNWRAP on the same call, will result in a
128
+ //BUFFER_UNDERFLOW if it needs data
129
+ }
130
+ //$FALL-THROUGH$
131
+ case NEED_UNWRAP: {
132
+ //perform the unwrap function
133
+ handshake = handshakeUnwrap(read);
134
+ if ( handshake.getStatus() == Status.OK ) {
135
+ if (handshakeStatus == HandshakeStatus.NEED_TASK)
136
+ handshakeStatus = tasks();
137
+ } else if ( handshake.getStatus() == Status.BUFFER_UNDERFLOW ){
138
+ //read more data, reregister for OP_READ
139
+ return SelectionKey.OP_READ;
140
+ } else {
141
+ throw new IOException("Invalid handshake status:"+handshakeStatus+" during handshake UNWRAP.");
142
+ }//switch
143
+ break;
144
+ }
145
+ case NEED_TASK: {
146
+ handshakeStatus = tasks();
147
+ break;
148
+ }
149
+ default: throw new IllegalStateException("Invalid handshake status:"+handshakeStatus);
150
+ }//switch
151
+ }//while
152
+ //return 0 if we are complete, otherwise reregister for any activity that
153
+ //would cause this method to be called again.
154
+ return handshakeComplete?0:(SelectionKey.OP_WRITE|SelectionKey.OP_READ);
155
+ }
156
+
157
+
158
+ /**
159
+ * Performs the WRAP function
160
+ * @param doWrite boolean
161
+ * @return SSLEngineResult
162
+ * @throws IOException
163
+ */
164
+ private SSLEngineResult handshakeWrap(boolean doWrite) throws IOException {
165
+ //this should never be called with a network buffer that contains data
166
+ //so we can clear it here.
167
+ netOutBuffer.clear();
168
+ //perform the wrap
169
+ SSLEngineResult result = sslEngine.wrap(emptyBuf, netOutBuffer);
170
+ //prepare the results to be written
171
+ netOutBuffer.flip();
172
+ //set the status
173
+ handshakeStatus = result.getHandshakeStatus();
174
+ //optimization, if we do have a writable channel, write it now
175
+ if ( doWrite ) flush(netOutBuffer);
176
+ return result;
177
+ }
178
+
179
+ /**
180
+ * Perform handshake unwrap
181
+ * @param doread boolean
182
+ * @return SSLEngineResult
183
+ * @throws IOException
184
+ */
185
+ private SSLEngineResult handshakeUnwrap(boolean doread) throws IOException {
186
+
187
+ if (netInBuffer.position() == netInBuffer.limit()) {
188
+ //clear the buffer if we have emptied it out on data
189
+ netInBuffer.clear();
190
+ }
191
+ if ( doread ) {
192
+ //if we have data to read, read it
193
+ int read = channel.read(netInBuffer);
194
+ if (read == -1) throw new IOException("EOF encountered during handshake.");
195
+ }
196
+ SSLEngineResult result;
197
+ boolean cont = false;
198
+ //loop while we can perform pure SSLEngine data
199
+ do {
200
+ //prepare the buffer with the incoming data
201
+ netInBuffer.flip();
202
+ //call unwrap
203
+ result = sslEngine.unwrap(netInBuffer, anotherBuffer);
204
+ //compact the buffer, this is an optional method, wonder what would happen if we didn't
205
+ netInBuffer.compact();
206
+ //read in the status
207
+ handshakeStatus = result.getHandshakeStatus();
208
+ if ( result.getStatus() == SSLEngineResult.Status.OK &&
209
+ result.getHandshakeStatus() == HandshakeStatus.NEED_TASK ) {
210
+ //execute tasks if we need to
211
+ handshakeStatus = tasks();
212
+ }
213
+ //perform another unwrap?
214
+ cont = result.getStatus() == SSLEngineResult.Status.OK &&
215
+ handshakeStatus == HandshakeStatus.NEED_UNWRAP;
216
+ }while ( cont );
217
+ return result;
218
+ }
219
+
220
+ /**
221
+ * Executes all the tasks needed on the same thread.
222
+ * @return HandshakeStatus
223
+ */
224
+ private SSLEngineResult.HandshakeStatus tasks() {
225
+ Runnable r = null;
226
+ while ( (r = sslEngine.getDelegatedTask()) != null) {
227
+ r.run();
228
+ }
229
+ return sslEngine.getHandshakeStatus();
230
+ }
231
+
232
+ protected boolean flush(ByteBuffer buf) throws IOException {
233
+ int remaining = buf.remaining();
234
+ if ( remaining > 0 ) {
235
+ int written = channel.write(buf);
236
+ return written >= remaining;
237
+ }else {
238
+ return true;
239
+ }
240
+ }
241
+
242
+ public javax.security.cert.X509Certificate getPeerCert() throws SSLPeerUnverifiedException {
243
+ return sslEngine.getSession().getPeerCertificateChain()[0];
244
+ }
245
+
246
+ public int write(ByteBuffer src) throws IOException {
247
+ if (!flush(netOutBuffer)) return 0;
248
+
249
+ netOutBuffer.clear();
250
+
251
+ SSLEngineResult result = sslEngine.wrap(src, netOutBuffer);
252
+ int written = result.bytesConsumed();
253
+ netOutBuffer.flip();
254
+
255
+ if (result.getStatus() == Status.OK) {
256
+ if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK)
257
+ tasks();
258
+ } else {
259
+ throw new IOException("Unable to wrap data, invalid engine state: " +result.getStatus());
260
+ }
261
+
262
+ //force a flush
263
+ flush(netOutBuffer);
264
+
265
+ return written;
266
+ }
267
+
268
+ public int read(ByteBuffer dst) throws IOException {
269
+ //did we finish our handshake?
270
+ if (!handshakeComplete) throw new IllegalStateException("Handshake incomplete, you must complete handshake before reading data.");
271
+
272
+ //read from the network
273
+ int netread = channel.read(netInBuffer);
274
+ //did we reach EOF? if so send EOF up one layer.
275
+ if (netread == -1) return -1;
276
+
277
+ //the data read
278
+ int read = 0;
279
+ //the SSL engine result
280
+ SSLEngineResult unwrap;
281
+ do {
282
+ //prepare the buffer
283
+ netInBuffer.flip();
284
+ //unwrap the data
285
+ unwrap = sslEngine.unwrap(netInBuffer, dst);
286
+ //compact the buffer
287
+ netInBuffer.compact();
288
+
289
+ if ( unwrap.getStatus()==Status.OK || unwrap.getStatus()==Status.BUFFER_UNDERFLOW ) {
290
+ //we did receive some data, add it to our total
291
+ read += unwrap.bytesProduced();
292
+ //perform any tasks if needed
293
+ if (unwrap.getHandshakeStatus() == HandshakeStatus.NEED_TASK) tasks();
294
+ //if we need more network data, then bail out for now.
295
+ if ( unwrap.getStatus() == Status.BUFFER_UNDERFLOW ) break;
296
+ }else if ( unwrap.getStatus()==Status.BUFFER_OVERFLOW && read>0 ) {
297
+ //buffer overflow can happen, if we have read data, then
298
+ //empty out the dst buffer before we do another read
299
+ break;
300
+ }else {
301
+ //here we should trap BUFFER_OVERFLOW and call expand on the buffer
302
+ //for now, throw an exception, as we initialized the buffers
303
+ //in the constructor
304
+ throw new IOException("Unable to unwrap data, invalid status: " + unwrap.getStatus());
305
+ }
306
+ } while ( (netInBuffer.position() != 0)); //continue to unwrapping as long as the input buffer has stuff
307
+ return (read);
308
+ }
309
+
310
+ }
@@ -0,0 +1,110 @@
1
+ # BufferedTokenizer takes a delimiter upon instantiation, or acts line-based
2
+ # by default. It allows input to be spoon-fed from some outside source which
3
+ # receives arbitrary length datagrams which may-or-may-not contain the token
4
+ # by which entities are delimited.
5
+ #
6
+ # By default, new BufferedTokenizers will operate on lines delimited by "\n" by default
7
+ # or allow you to specify any delimiter token you so choose, which will then
8
+ # be used by String#split to tokenize the input data
9
+ #
10
+ # @example Using BufferedTokernizer to parse lines out of incoming data
11
+ #
12
+ # module LineBufferedConnection
13
+ # def receive_data(data)
14
+ # (@buffer ||= BufferedTokenizer.new).extract(data).each do |line|
15
+ # receive_line(line)
16
+ # end
17
+ # end
18
+ # end
19
+ #
20
+ # @author Tony Arcieri
21
+ # @author Martin Emde
22
+ class BufferedTokenizer
23
+ # @param [String] delimiter
24
+ # @param [Integer] size_limit
25
+ def initialize(delimiter = "\n", size_limit = nil)
26
+ @delimiter = delimiter
27
+ @size_limit = size_limit
28
+
29
+ # The input buffer is stored as an array. This is by far the most efficient
30
+ # approach given language constraints (in C a linked list would be a more
31
+ # appropriate data structure). Segments of input data are stored in a list
32
+ # which is only joined when a token is reached, substantially reducing the
33
+ # number of objects required for the operation.
34
+ @input = []
35
+
36
+ # Size of the input buffer
37
+ @input_size = 0
38
+ end
39
+
40
+ # Extract takes an arbitrary string of input data and returns an array of
41
+ # tokenized entities, provided there were any available to extract.
42
+ #
43
+ # @example
44
+ #
45
+ # tokenizer.extract(data).
46
+ # map { |entity| Decode(entity) }.each { ... }
47
+ #
48
+ # @param [String] data
49
+ def extract(data)
50
+ # Extract token-delimited entities from the input string with the split command.
51
+ # There's a bit of craftiness here with the -1 parameter. Normally split would
52
+ # behave no differently regardless of if the token lies at the very end of the
53
+ # input buffer or not (i.e. a literal edge case) Specifying -1 forces split to
54
+ # return "" in this case, meaning that the last entry in the list represents a
55
+ # new segment of data where the token has not been encountered
56
+ entities = data.split @delimiter, -1
57
+
58
+ # Check to see if the buffer has exceeded capacity, if we're imposing a limit
59
+ if @size_limit
60
+ raise 'input buffer full' if @input_size + entities.first.size > @size_limit
61
+ @input_size += entities.first.size
62
+ end
63
+
64
+ # Move the first entry in the resulting array into the input buffer. It represents
65
+ # the last segment of a token-delimited entity unless it's the only entry in the list.
66
+ @input << entities.shift
67
+
68
+ # If the resulting array from the split is empty, the token was not encountered
69
+ # (not even at the end of the buffer). Since we've encountered no token-delimited
70
+ # entities this go-around, return an empty array.
71
+ return [] if entities.empty?
72
+
73
+ # At this point, we've hit a token, or potentially multiple tokens. Now we can bring
74
+ # together all the data we've buffered from earlier calls without hitting a token,
75
+ # and add it to our list of discovered entities.
76
+ entities.unshift @input.join
77
+
78
+ # Now that we've hit a token, joined the input buffer and added it to the entities
79
+ # list, we can go ahead and clear the input buffer. All of the segments that were
80
+ # stored before the join can now be garbage collected.
81
+ @input.clear
82
+
83
+ # The last entity in the list is not token delimited, however, thanks to the -1
84
+ # passed to split. It represents the beginning of a new list of as-yet-untokenized
85
+ # data, so we add it to the start of the list.
86
+ @input << entities.pop
87
+
88
+ # Set the new input buffer size, provided we're keeping track
89
+ @input_size = @input.first.size if @size_limit
90
+
91
+ # Now we're left with the list of extracted token-delimited entities we wanted
92
+ # in the first place. Hooray!
93
+ entities
94
+ end
95
+
96
+ # Flush the contents of the input buffer, i.e. return the input buffer even though
97
+ # a token has not yet been encountered.
98
+ #
99
+ # @return [String]
100
+ def flush
101
+ buffer = @input.join
102
+ @input.clear
103
+ buffer
104
+ end
105
+
106
+ # @return [Boolean]
107
+ def empty?
108
+ @input.empty?
109
+ end
110
+ end
@@ -0,0 +1,58 @@
1
+ module EventMachine
2
+ # Utility method for coercing arguments to an object that responds to :call.
3
+ # Accepts an object and a method name to send to, or a block, or an object
4
+ # that responds to :call.
5
+ #
6
+ # @example EventMachine.Callback used with a block. Returns that block.
7
+ #
8
+ # cb = EventMachine.Callback do |msg|
9
+ # puts(msg)
10
+ # end
11
+ # # returned object is a callable
12
+ # cb.call('hello world')
13
+ #
14
+ #
15
+ # @example EventMachine.Callback used with an object (to be more specific, class object) and a method name, returns an object that responds to #call
16
+ #
17
+ # cb = EventMachine.Callback(Object, :puts)
18
+ # # returned object is a callable that delegates to Kernel#puts (in this case Object.puts)
19
+ # cb.call('hello world')
20
+ #
21
+ #
22
+ # @example EventMachine.Callback used with an object that responds to #call. Returns the argument.
23
+ #
24
+ # cb = EventMachine.Callback(proc{ |msg| puts(msg) })
25
+ # # returned object is a callable
26
+ # cb.call('hello world')
27
+ #
28
+ #
29
+ # @overload Callback(object, method)
30
+ # Wraps `method` invocation on `object` into an object that responds to #call that proxies all the arguments to that method
31
+ # @param [Object] Object to invoke method on
32
+ # @param [Symbol] Method name
33
+ # @return [<#call>] An object that responds to #call that takes any number of arguments and invokes method on object with those arguments
34
+ #
35
+ # @overload Callback(object)
36
+ # Returns callable object as is, without any coercion
37
+ # @param [<#call>] An object that responds to #call
38
+ # @return [<#call>] Its argument
39
+ #
40
+ # @overload Callback(&block)
41
+ # Returns block passed to it without any coercion
42
+ # @return [<#call>] Block passed to this method
43
+ #
44
+ # @raise [ArgumentError] When argument doesn't respond to #call, method name is missing or when invoked without arguments and block isn't given
45
+ #
46
+ # @return [<#call>]
47
+ def self.Callback(object = nil, method = nil, &blk)
48
+ if object && method
49
+ lambda { |*args| object.__send__ method, *args }
50
+ else
51
+ if object.respond_to? :call
52
+ object
53
+ else
54
+ blk || raise(ArgumentError)
55
+ end # if
56
+ end # if
57
+ end # self.Callback
58
+ end # EventMachine