msgpack 1.4.3 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yaml +7 -6
  3. data/ChangeLog +18 -0
  4. data/README.md +22 -0
  5. data/Rakefile +1 -2
  6. data/doclib/msgpack/factory.rb +46 -3
  7. data/doclib/msgpack/packer.rb +5 -4
  8. data/doclib/msgpack/unpacker.rb +2 -2
  9. data/ext/java/org/msgpack/jruby/Buffer.java +6 -0
  10. data/ext/java/org/msgpack/jruby/Decoder.java +23 -19
  11. data/ext/java/org/msgpack/jruby/Encoder.java +45 -18
  12. data/ext/java/org/msgpack/jruby/ExtensionRegistry.java +28 -40
  13. data/ext/java/org/msgpack/jruby/Factory.java +40 -5
  14. data/ext/java/org/msgpack/jruby/Packer.java +21 -11
  15. data/ext/java/org/msgpack/jruby/Unpacker.java +44 -22
  16. data/ext/msgpack/buffer.h +2 -2
  17. data/ext/msgpack/buffer_class.c +23 -15
  18. data/ext/msgpack/factory_class.c +78 -16
  19. data/ext/msgpack/packer.c +42 -5
  20. data/ext/msgpack/packer.h +24 -0
  21. data/ext/msgpack/packer_class.c +29 -22
  22. data/ext/msgpack/packer_ext_registry.c +23 -9
  23. data/ext/msgpack/packer_ext_registry.h +38 -31
  24. data/ext/msgpack/unpacker.c +37 -19
  25. data/ext/msgpack/unpacker.h +2 -2
  26. data/ext/msgpack/unpacker_class.c +26 -45
  27. data/ext/msgpack/unpacker_ext_registry.c +40 -16
  28. data/ext/msgpack/unpacker_ext_registry.h +21 -14
  29. data/lib/msgpack/bigint.rb +69 -0
  30. data/lib/msgpack/factory.rb +103 -0
  31. data/lib/msgpack/symbol.rb +8 -1
  32. data/lib/msgpack/version.rb +1 -1
  33. data/lib/msgpack.rb +4 -5
  34. data/msgpack.gemspec +1 -2
  35. data/spec/bigint_spec.rb +26 -0
  36. data/spec/factory_spec.rb +263 -14
  37. data/spec/spec_helper.rb +3 -4
  38. data/spec/timestamp_spec.rb +0 -2
  39. data/spec/unpacker_spec.rb +22 -3
  40. metadata +7 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 750c05b37865e3da4e2ca909840dfb4afd732799f756a89465c26f2e6e77ef91
4
- data.tar.gz: afc8dfd5103e65068fa054d7daf2811c65fbb7627e2986a1a50ad0307d643113
3
+ metadata.gz: c9e04f0f240684f2b81a5f00f9afd951feac68ee06cf4dd6e229fa8d5925c5b0
4
+ data.tar.gz: 8bf61d049bcf041bcff6e33631dbc3a821de096aca8b1c5069478934a49cd2e6
5
5
  SHA512:
6
- metadata.gz: 01af679c600ae449256f30a3beb5f9993266e1e8bcd11e5ec8e635301a713c3175131e112540660d8adc67ae06470043eee48d1a3b1ecc69b2e436eb0010579c
7
- data.tar.gz: 13a82671e440447415c22d930dd795d4f1d6e07ea9328078ed25c887890bbc27080f38fb5d33da6cb15dc8dac2789e3a930548a64d298b1ca44b510bb3978d5f
6
+ metadata.gz: 58acbb7f3c139dfa153b21445242d202335dea3b1dab6f3f8cd6eac9d427122ea6ec9d4629a5f82674dda53fd9f8b546051d704555c1e1db40c566d9cb617ff9
7
+ data.tar.gz: cb3ec4aba6ef01722ef39fc3188ed43f1f3305daae900dc7bab2a15ff1e074512f75b482331c9d962f8e9ce258bee93bf2d6dbfb29cd2a0c8abccd3fcff58fd8
@@ -12,6 +12,7 @@ on:
12
12
  jobs:
13
13
  mri:
14
14
  strategy:
15
+ fail-fast: false
15
16
  matrix:
16
17
  os: [ubuntu, macos, windows]
17
18
  ruby: ['2.4', '2.5', '2.6', '2.7', '3.0', '3.1']
@@ -21,28 +22,28 @@ jobs:
21
22
  - uses: ruby/setup-ruby@v1
22
23
  with:
23
24
  ruby-version: ${{ matrix.ruby }}
24
- - run: bundle install
25
+ bundler-cache: true # 'bundle install' and cache
25
26
  - run: bundle exec rake
26
27
 
27
28
  jruby:
28
29
  strategy:
30
+ fail-fast: false
29
31
  matrix:
30
32
  os: [ubuntu]
31
- # TODO: update to 9.3.3.0 once supported
32
- # https://github.com/ruby/setup-ruby#supported-versions
33
- ruby: ['jruby-9.2.19.0', 'jruby-9.3.2.0']
33
+ ruby: ['jruby-9.2.19.0', 'jruby-9.3.3.0']
34
34
  runs-on: ${{ matrix.os }}-latest
35
35
  steps:
36
36
  - uses: actions/checkout@v2
37
37
  - uses: ruby/setup-ruby@v1
38
38
  with:
39
39
  ruby-version: ${{ matrix.ruby }}
40
- - run: bundle install
40
+ bundler-cache: true # 'bundle install' and cache
41
41
  - run: bundle exec rake
42
42
 
43
43
  head-versions:
44
44
  continue-on-error: true
45
45
  strategy:
46
+ fail-fast: false
46
47
  matrix:
47
48
  os: [ubuntu]
48
49
  ruby: ['ruby-head', 'jruby-head', 'truffleruby']
@@ -52,5 +53,5 @@ jobs:
52
53
  - uses: ruby/setup-ruby@v1
53
54
  with:
54
55
  ruby-version: ${{ matrix.ruby }}
55
- - run: bundle install
56
+ bundler-cache: true # 'bundle install' and cache
56
57
  - run: bundle exec rake || echo "failed, but ignore it"
data/ChangeLog CHANGED
@@ -1,3 +1,21 @@
1
+ 2022-04-06 version 1.5.0:
2
+
3
+ * Add recursive option on Factory#register_type to operate Packer/Unpacker manually
4
+ * Add oversized_integer_extension option on Factory#register_type to pack/unpack bigint using ext types
5
+ * Add Factory#pool method and Factory::Pool class to provide pooled Packer and Unpacker instances
6
+
7
+ 2022-02-15 version 1.4.5:
8
+
9
+ * Fix to create UTF-8 Symbol keys when symbolize_keys: true
10
+ * Fix to assume Symbols as US-ASCII or UTF-8
11
+ * Optimize Packer/Unpacker initialization
12
+ * Optimize extension class lookup
13
+ * Rename Packer#clear as Packer#reset (#clear is still available as an alias)
14
+
15
+ 2022-01-22 version 1.4.4:
16
+
17
+ * Specify the build option --platform=8 for older Java platforms
18
+
1
19
  2022-01-20 version 1.4.3:
2
20
 
3
21
  * Optimize serialization/deserialization of Symbols
data/README.md CHANGED
@@ -187,6 +187,28 @@ MessagePack::DefaultFactory.register_type(0x03, MyClass3)
187
187
  MessagePack.unpack(data_with_ext_typeid_03) #=> MyClass3 instance
188
188
  ```
189
189
 
190
+ Alternatively, extension types can call the packer or unpacker recursively to generate the extension data:
191
+
192
+ ```ruby
193
+ Point = Struct.new(:x, :y)
194
+ factory = MessagePack::Factory.new
195
+ factory.register_type(
196
+ 0x01,
197
+ Point,
198
+ packer: ->(point, packer) {
199
+ packer.write(point.x)
200
+ packer.write(point.y)
201
+ },
202
+ unpacker: ->(unpacker) {
203
+ x = unpacker.read
204
+ y = unpacker.read
205
+ Point.new(x, y)
206
+ },
207
+ recursive: true,
208
+ )
209
+ factory.load(factory.dump(Point.new(12, 34))) # => #<struct Point x=12, y=34>
210
+ ```
211
+
190
212
  ## Buffer API
191
213
 
192
214
  MessagePack for Ruby provides a buffer API so that you can read or write data by hand, not via Packer or Unpacker API.
data/Rakefile CHANGED
@@ -34,8 +34,7 @@ if RUBY_PLATFORM =~ /java/
34
34
  jars = ["#{jruby_home}/lib/jruby.jar"]
35
35
  ext.classpath = jars.map { |x| File.expand_path(x) }.join(':')
36
36
  ext.lib_dir = File.join(*['lib', 'msgpack', ENV['FAT_DIR']].compact)
37
- ext.source_version = '1.8'
38
- ext.target_version = '1.8'
37
+ ext.release = '8'
39
38
  end
40
39
  else
41
40
  require 'rake/extensiontask'
@@ -31,7 +31,7 @@ module MessagePack
31
31
  #
32
32
  # See Packer#initialize for supported options.
33
33
  #
34
- def dump(obj, options={})
34
+ def dump(obj, options=nil)
35
35
  end
36
36
  alias pack dump
37
37
 
@@ -57,13 +57,13 @@ module MessagePack
57
57
  #
58
58
  # See Unpacker#initialize for supported options.
59
59
  #
60
- def load(data, options={})
60
+ def load(data, options=nil)
61
61
  end
62
62
  alias unpack load
63
63
 
64
64
  #
65
65
  # Register a type and Class to be registered for packer and/or unpacker.
66
- # If options are not speicified, factory will use :to_msgpack_ext for packer, and
66
+ # If options are not specified, factory will use :to_msgpack_ext for packer, and
67
67
  # :from_msgpack_ext for unpacker.
68
68
  #
69
69
  # @param type [Fixnum] type id of registered Class (0-127)
@@ -76,6 +76,7 @@ module MessagePack
76
76
  # * *:packer* specify symbol or proc object for packer
77
77
  # * *:unpacker* specify symbol or proc object for unpacker
78
78
  # * *:optimized_symbols_parsing* specify true to use the optimized symbols parsing (not supported on JRuby now)
79
+ # * *recursive* specify true to receive the packer or unpacker as argument to generate the extension body manually.
79
80
  #
80
81
  def register_type(type, klass, options={})
81
82
  end
@@ -98,5 +99,47 @@ module MessagePack
98
99
  #
99
100
  def type_registered?(klass_or_type, selector=:both)
100
101
  end
102
+
103
+ #
104
+ # Creates a MessagePack::PooledFactory instance of the given size.
105
+ #
106
+ # PooledFactory keeps Packer and Unpacker instance in a pool for improved performance.
107
+ # Note that the size defines how many instances are kept in cache, not the maximum of instances
108
+ # that can be created. If the pool limit is reached, a new instance is created anyway.
109
+ #
110
+ # @param size [Fixnum] specify how many Packer and Unpacker to keep in cache (default 1)
111
+ # @param options [Hash] Combined options for Packer and Unpacker. See Packer#initialize and Unpacker#initialize
112
+ # for supported options.
113
+ def pool(size=1, **options)
114
+ end
115
+
116
+ class Pool
117
+ #
118
+ # Deserializes an object from the string or io and returns it.
119
+ #
120
+ # If there're not enough data to deserialize one object, this method raises EOFError.
121
+ # If data format is invalid, this method raises MessagePack::MalformedFormatError.
122
+ # If the object nests too deeply, this method raises MessagePack::StackError.
123
+ #
124
+ # @param data [String]
125
+ # @return [Object] deserialized object
126
+ #
127
+ # See Unpacker#initialize for supported options.
128
+ #
129
+ def load(data)
130
+ end
131
+
132
+ #
133
+ # Serialize the passed value
134
+ #
135
+ # If it could not serialize the object, it raises
136
+ # NoMethodError: undefined method `to_msgpack' for #<the_object>.
137
+ #
138
+ # @param obj [Object] object to serialize
139
+ # @return [String] serialized object
140
+ #
141
+ def dump(object)
142
+ end
143
+ end
101
144
  end
102
145
  end
@@ -14,7 +14,7 @@ module MessagePack
14
14
  # @overload initialize(io, options={})
15
15
  # @param io [IO]
16
16
  # @param options [Hash]
17
- # This packer writes serialzied objects into the IO when the internal buffer is filled.
17
+ # This packer writes serialized objects into the IO when the internal buffer is filled.
18
18
  # _io_ must respond to write(string) or append(string) method.
19
19
  #
20
20
  # Supported options:
@@ -33,12 +33,12 @@ module MessagePack
33
33
  #
34
34
  # @overload register_type(type, klass, &block)
35
35
  # @param type [Fixnum] type id (0-127) user defined type id for specified Class
36
- # @param klass [Class] Class to be serialized with speicifed type id
36
+ # @param klass [Class] Class to be serialized with specified type id
37
37
  # @yieldparam object [Object] object to be serialized
38
38
  #
39
39
  # @overload register_type(type, klass, method_name)
40
40
  # @param type [Fixnum] type id (0-127) user defined type id for specified Class
41
- # @param klass [Class] Class to be serialized with speicifed type id
41
+ # @param klass [Class] Class to be serialized with specified type id
42
42
  # @param method_name [Symbol] method which returns bytes of serialized representation
43
43
  #
44
44
  # @return nil
@@ -155,8 +155,9 @@ module MessagePack
155
155
  #
156
156
  # @return nil
157
157
  #
158
- def clear
158
+ def reset
159
159
  end
160
+ alias clear reset
160
161
 
161
162
  #
162
163
  # Returns size of the internal buffer. Same as buffer.size.
@@ -36,7 +36,7 @@ module MessagePack
36
36
  #
37
37
  # @overload register_type(type, klass, class_method_name)
38
38
  # @param type [Fixnum] type id (0-127) user defined type id for specified Class
39
- # @param klass [Class] Class to be serialized with speicifed type id
39
+ # @param klass [Class] Class to be serialized with specified type id
40
40
  # @param class_method_name [Symbol] class method which returns an instance object
41
41
  #
42
42
  # @return nil
@@ -149,7 +149,7 @@ module MessagePack
149
149
  #
150
150
  # It repeats until the io or internal buffer does not include any complete objects.
151
151
  #
152
- # If the an IO is set, it repeats to read data from the IO when the buffer
152
+ # If an IO is set, it repeats to read data from the IO when the buffer
153
153
  # becomes empty until the IO raises EOFError.
154
154
  #
155
155
  # This method could raise same errors with _read_ excepting EOFError.
@@ -224,4 +224,10 @@ public class Buffer extends RubyObject {
224
224
  public IRubyObject writeTo(ThreadContext ctx, IRubyObject io) {
225
225
  return io.callMethod(ctx, "write", readCommon(ctx, null, false));
226
226
  }
227
+
228
+ public ByteList getBytes() {
229
+ byte[] bytes = new byte[rawSize()];
230
+ buffer.get(bytes);
231
+ return new ByteList(bytes, binaryEncoding);
232
+ }
227
233
  }
@@ -14,6 +14,7 @@ import org.jruby.RubyBignum;
14
14
  import org.jruby.RubyString;
15
15
  import org.jruby.RubyArray;
16
16
  import org.jruby.RubyHash;
17
+ import org.jruby.RubyInteger;
17
18
  import org.jruby.exceptions.RaiseException;
18
19
  import org.jruby.runtime.builtin.IRubyObject;
19
20
  import org.jruby.util.ByteList;
@@ -35,7 +36,7 @@ public class Decoder implements Iterator<IRubyObject> {
35
36
  private final RubyClass unexpectedTypeErrorClass;
36
37
  private final RubyClass unknownExtTypeErrorClass;
37
38
 
38
- private ExtensionRegistry registry;
39
+ private Unpacker unpacker;
39
40
  private ByteBuffer buffer;
40
41
  private boolean symbolizeKeys;
41
42
  private boolean freeze;
@@ -45,29 +46,29 @@ public class Decoder implements Iterator<IRubyObject> {
45
46
  this(runtime, null, new byte[] {}, 0, 0, false, false, false);
46
47
  }
47
48
 
48
- public Decoder(Ruby runtime, ExtensionRegistry registry) {
49
- this(runtime, registry, new byte[] {}, 0, 0, false, false, false);
49
+ public Decoder(Ruby runtime, Unpacker unpacker) {
50
+ this(runtime, unpacker, new byte[] {}, 0, 0, false, false, false);
50
51
  }
51
52
 
52
53
  public Decoder(Ruby runtime, byte[] bytes) {
53
54
  this(runtime, null, bytes, 0, bytes.length, false, false, false);
54
55
  }
55
56
 
56
- public Decoder(Ruby runtime, ExtensionRegistry registry, byte[] bytes) {
57
- this(runtime, registry, bytes, 0, bytes.length, false, false, false);
57
+ public Decoder(Ruby runtime, Unpacker unpacker, byte[] bytes) {
58
+ this(runtime, unpacker, bytes, 0, bytes.length, false, false, false);
58
59
  }
59
60
 
60
- public Decoder(Ruby runtime, ExtensionRegistry registry, byte[] bytes, boolean symbolizeKeys, boolean freeze, boolean allowUnknownExt) {
61
- this(runtime, registry, bytes, 0, bytes.length, symbolizeKeys, freeze, allowUnknownExt);
61
+ public Decoder(Ruby runtime, Unpacker unpacker, byte[] bytes, boolean symbolizeKeys, boolean freeze, boolean allowUnknownExt) {
62
+ this(runtime, unpacker, bytes, 0, bytes.length, symbolizeKeys, freeze, allowUnknownExt);
62
63
  }
63
64
 
64
- public Decoder(Ruby runtime, ExtensionRegistry registry, byte[] bytes, int offset, int length) {
65
- this(runtime, registry, bytes, offset, length, false, false, false);
65
+ public Decoder(Ruby runtime, Unpacker unpacker, byte[] bytes, int offset, int length) {
66
+ this(runtime, unpacker, bytes, offset, length, false, false, false);
66
67
  }
67
68
 
68
- public Decoder(Ruby runtime, ExtensionRegistry registry, byte[] bytes, int offset, int length, boolean symbolizeKeys, boolean freeze, boolean allowUnknownExt) {
69
+ public Decoder(Ruby runtime, Unpacker unpacker, byte[] bytes, int offset, int length, boolean symbolizeKeys, boolean freeze, boolean allowUnknownExt) {
69
70
  this.runtime = runtime;
70
- this.registry = registry;
71
+ this.unpacker = unpacker;
71
72
  this.symbolizeKeys = symbolizeKeys;
72
73
  this.freeze = freeze;
73
74
  this.allowUnknownExt = allowUnknownExt;
@@ -154,18 +155,21 @@ public class Decoder implements Iterator<IRubyObject> {
154
155
 
155
156
  private IRubyObject consumeExtension(int size) {
156
157
  int type = buffer.get();
157
- byte[] payload = readBytes(size);
158
-
159
- if (registry != null) {
160
- IRubyObject proc = registry.lookupUnpackerByTypeId(type);
161
- if (proc != null) {
162
- ByteList byteList = new ByteList(payload, runtime.getEncodingService().getAscii8bitEncoding());
163
- return proc.callMethod(runtime.getCurrentContext(), "call", runtime.newString(byteList));
158
+ if (unpacker != null) {
159
+ ExtensionRegistry.ExtensionEntry entry = unpacker.lookupExtensionByTypeId(type);
160
+ if (entry != null) {
161
+ IRubyObject proc = entry.getUnpackerProc();
162
+ if (entry.isRecursive()) {
163
+ return proc.callMethod(runtime.getCurrentContext(), "call", unpacker);
164
+ } else {
165
+ ByteList byteList = new ByteList(readBytes(size), runtime.getEncodingService().getAscii8bitEncoding());
166
+ return proc.callMethod(runtime.getCurrentContext(), "call", runtime.newString(byteList));
167
+ }
164
168
  }
165
169
  }
166
170
 
167
171
  if (this.allowUnknownExt) {
168
- return ExtensionValue.newExtensionValue(runtime, type, payload);
172
+ return ExtensionValue.newExtensionValue(runtime, type, readBytes(size));
169
173
  }
170
174
 
171
175
  throw runtime.newRaiseException(unknownExtTypeErrorClass, "unexpected extension type");
@@ -38,12 +38,16 @@ public class Encoder {
38
38
  private final Encoding utf8Encoding;
39
39
  private final boolean compatibilityMode;
40
40
  private final ExtensionRegistry registry;
41
+ private final Packer packer;
41
42
 
42
43
  public boolean hasSymbolExtType;
44
+ private boolean hasBigintExtType;
45
+ private boolean recursiveExtension;
43
46
 
44
47
  private ByteBuffer buffer;
45
48
 
46
- public Encoder(Ruby runtime, boolean compatibilityMode, ExtensionRegistry registry, boolean hasSymbolExtType) {
49
+ public Encoder(Ruby runtime, Packer packer, boolean compatibilityMode, ExtensionRegistry registry, boolean hasSymbolExtType, boolean hasBigintExtType) {
50
+ this.packer = packer;
47
51
  this.runtime = runtime;
48
52
  this.buffer = ByteBuffer.allocate(CACHE_LINE_SIZE - ARRAY_HEADER_SIZE);
49
53
  this.binaryEncoding = runtime.getEncodingService().getAscii8bitEncoding();
@@ -51,6 +55,7 @@ public class Encoder {
51
55
  this.compatibilityMode = compatibilityMode;
52
56
  this.registry = registry;
53
57
  this.hasSymbolExtType = hasSymbolExtType;
58
+ this.hasBigintExtType = hasBigintExtType;
54
59
  }
55
60
 
56
61
  public boolean isCompatibilityMode() {
@@ -66,9 +71,17 @@ public class Encoder {
66
71
  }
67
72
 
68
73
  private IRubyObject readRubyString() {
69
- IRubyObject str = runtime.newString(new ByteList(buffer.array(), 0, buffer.position(), binaryEncoding, false));
70
- buffer.clear();
71
- return str;
74
+ if (recursiveExtension) {
75
+ // If recursiveExtension is true, it means we re-entered encode, so we MUST NOT flush the buffer.
76
+ // Instead we return an empty string to act as a null object for the caller. The buffer will actually
77
+ // be flushed once we're done serializing the recursive extension.
78
+ // All other method that consume the buffer should do so through readRubyString or implement the same logic.
79
+ return runtime.newString();
80
+ } else {
81
+ IRubyObject str = runtime.newString(new ByteList(buffer.array(), 0, buffer.position(), binaryEncoding, false));
82
+ buffer.clear();
83
+ return str;
84
+ }
72
85
  }
73
86
 
74
87
  public IRubyObject encode(IRubyObject object) {
@@ -147,7 +160,10 @@ public class Encoder {
147
160
  BigInteger value = object.getBigIntegerValue();
148
161
  if (value.compareTo(RubyBignum.LONG_MIN) < 0 || value.compareTo(RubyBignum.LONG_MAX) > 0) {
149
162
  if (value.bitLength() > 64 || (value.bitLength() > 63 && value.signum() < 0)) {
150
- throw runtime.newArgumentError(String.format("Cannot pack big integer: %s", value));
163
+ if (hasBigintExtType && tryAppendWithExtTypeLookup(object)) {
164
+ return;
165
+ }
166
+ throw runtime.newRangeError(String.format("Cannot pack big integer: %s", value));
151
167
  }
152
168
  ensureRemainingCapacity(9);
153
169
  buffer.put(value.signum() < 0 ? INT64 : UINT64);
@@ -391,19 +407,30 @@ public class Encoder {
391
407
 
392
408
  private boolean tryAppendWithExtTypeLookup(IRubyObject object) {
393
409
  if (registry != null) {
394
- RubyModule lookupClass;
395
-
396
- if (object.getType() == runtime.getSymbol()) {
397
- lookupClass = object.getType();
398
- } else {
399
- lookupClass = object.getSingletonClass();
400
- }
401
-
402
- IRubyObject[] pair = registry.lookupPackerForObject(object);
403
- if (pair != null) {
404
- RubyString bytes = pair[0].callMethod(runtime.getCurrentContext(), "call", object).asString();
405
- int type = (int) ((RubyFixnum) pair[1]).getLongValue();
406
- appendExt(type, bytes.getByteList());
410
+ ExtensionRegistry.ExtensionEntry entry = registry.lookupExtensionForObject(object);
411
+ if (entry != null) {
412
+ IRubyObject proc = entry.getPackerProc();
413
+ int type = entry.getTypeId();
414
+
415
+ if (entry.isRecursive()) {
416
+ ByteBuffer oldBuffer = buffer;
417
+ buffer = ByteBuffer.allocate(CACHE_LINE_SIZE - ARRAY_HEADER_SIZE);
418
+ recursiveExtension = true;
419
+
420
+ ByteList payload;
421
+ try {
422
+ IRubyObject args[] = { object, packer };
423
+ proc.callMethod(runtime.getCurrentContext(), "call", args);
424
+ payload = new ByteList(buffer.array(), 0, buffer.position(), binaryEncoding, false);
425
+ } finally {
426
+ recursiveExtension = false;
427
+ buffer = oldBuffer;
428
+ }
429
+ appendExt(type, payload);
430
+ } else {
431
+ RubyString bytes = proc.callMethod(runtime.getCurrentContext(), "call", object).asString();
432
+ appendExt(type, bytes.getByteList());
433
+ }
407
434
  return true;
408
435
  }
409
436
  }
@@ -59,64 +59,46 @@ public class ExtensionRegistry {
59
59
  return hash;
60
60
  }
61
61
 
62
- public void put(RubyModule mod, int typeId, IRubyObject packerProc, IRubyObject packerArg, IRubyObject unpackerProc, IRubyObject unpackerArg) {
63
- ExtensionEntry entry = new ExtensionEntry(mod, typeId, packerProc, packerArg, unpackerProc, unpackerArg);
62
+ public void put(RubyModule mod, int typeId, boolean recursive, IRubyObject packerProc, IRubyObject packerArg, IRubyObject unpackerProc, IRubyObject unpackerArg) {
63
+ ExtensionEntry entry = new ExtensionEntry(mod, typeId, recursive, packerProc, packerArg, unpackerProc, unpackerArg);
64
64
  extensionsByModule.put(mod, entry);
65
65
  extensionsByTypeId[typeId + 128] = entry;
66
66
  extensionsByAncestor.clear();
67
67
  }
68
68
 
69
- public IRubyObject lookupUnpackerByTypeId(int typeId) {
69
+ public ExtensionEntry lookupExtensionByTypeId(int typeId) {
70
70
  ExtensionEntry e = extensionsByTypeId[typeId + 128];
71
71
  if (e != null && e.hasUnpacker()) {
72
- return e.getUnpackerProc();
73
- } else {
74
- return null;
72
+ return e;
75
73
  }
74
+ return null;
76
75
  }
77
76
 
78
- public IRubyObject[] lookupPackerForObject(IRubyObject object) {
77
+ public ExtensionEntry lookupExtensionForObject(IRubyObject object) {
79
78
  RubyModule lookupClass = null;
80
- IRubyObject[] pair;
79
+ ExtensionEntry entry = null;
81
80
  /*
82
81
  * Objects of type Integer (Fixnum, Bignum), Float, Symbol and frozen
83
82
  * String have no singleton class and raise a TypeError when trying to get
84
83
  * it.
85
- *
86
- * Since all but symbols are already filtered out when reaching this code
87
- * only symbols are checked here.
88
84
  */
89
- if (!(object instanceof RubySymbol)) {
90
- lookupClass = object.getSingletonClass();
91
- pair = fetchEntryByModule(lookupClass);
92
- if (pair != null) {
93
- return pair;
94
- }
85
+ lookupClass = object.getMetaClass();
86
+ entry = extensionsByModule.get(lookupClass);
87
+ if (entry != null && entry.hasPacker()) {
88
+ return entry;
95
89
  }
96
90
 
97
- pair = fetchEntryByModule(object.getType());
98
- if (pair != null) {
99
- return pair;
91
+ RubyModule realClass = object.getType();
92
+ if (realClass != lookupClass) {
93
+ entry = extensionsByModule.get(realClass);
94
+ if (entry != null && entry.hasPacker()) {
95
+ return entry;
96
+ }
100
97
  }
101
98
 
102
- if (lookupClass == null) {
103
- lookupClass = object.getType(); // only for Symbol
104
- }
105
- ExtensionEntry e = findEntryByModuleOrAncestor(lookupClass);
106
- if (e != null && e.hasPacker()) {
107
- extensionsByAncestor.put(e.getExtensionModule(), e);
108
- return e.toPackerProcTypeIdPair(lookupClass.getRuntime().getCurrentContext());
109
- }
110
- return null;
111
- }
112
-
113
- private IRubyObject[] fetchEntryByModule(final RubyModule mod) {
114
- ExtensionEntry e = extensionsByModule.get(mod);
115
- if (e == null) {
116
- e = extensionsByAncestor.get(mod);
117
- }
118
- if (e != null && e.hasPacker()) {
119
- return e.toPackerProcTypeIdPair(mod.getRuntime().getCurrentContext());
99
+ entry = findEntryByModuleOrAncestor(lookupClass);
100
+ if (entry != null && entry.hasPacker()) {
101
+ return entry;
120
102
  }
121
103
  return null;
122
104
  }
@@ -132,17 +114,19 @@ public class ExtensionRegistry {
132
114
  return null;
133
115
  }
134
116
 
135
- private static class ExtensionEntry {
117
+ public static class ExtensionEntry {
136
118
  private final RubyModule mod;
137
119
  private final int typeId;
120
+ private final boolean recursive;
138
121
  private final IRubyObject packerProc;
139
122
  private final IRubyObject packerArg;
140
123
  private final IRubyObject unpackerProc;
141
124
  private final IRubyObject unpackerArg;
142
125
 
143
- public ExtensionEntry(RubyModule mod, int typeId, IRubyObject packerProc, IRubyObject packerArg, IRubyObject unpackerProc, IRubyObject unpackerArg) {
126
+ public ExtensionEntry(RubyModule mod, int typeId, boolean recursive, IRubyObject packerProc, IRubyObject packerArg, IRubyObject unpackerProc, IRubyObject unpackerArg) {
144
127
  this.mod = mod;
145
128
  this.typeId = typeId;
129
+ this.recursive = recursive;
146
130
  this.packerProc = packerProc;
147
131
  this.packerArg = packerArg;
148
132
  this.unpackerProc = unpackerProc;
@@ -157,6 +141,10 @@ public class ExtensionRegistry {
157
141
  return typeId;
158
142
  }
159
143
 
144
+ public boolean isRecursive() {
145
+ return recursive;
146
+ }
147
+
160
148
  public boolean hasPacker() {
161
149
  return packerProc != null;
162
150
  }