msgpack 1.4.0.pre1 → 1.4.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dce37069bf08ffa0b29a76363cee45e3d8e19188236d673dc1c9b4172727738c
4
- data.tar.gz: 3c30d3ac0b3a3b4f5432e958935370f2e55d16ce516bf6a91eb12382aad38d8b
3
+ metadata.gz: 750c05b37865e3da4e2ca909840dfb4afd732799f756a89465c26f2e6e77ef91
4
+ data.tar.gz: afc8dfd5103e65068fa054d7daf2811c65fbb7627e2986a1a50ad0307d643113
5
5
  SHA512:
6
- metadata.gz: 53d062f57dcd7324a04224c16dedbe7aa0188515d595f5a38ca543e0b956db8bbd3906a59cf3faceb5c0fb3b23d10be47bb34d19146c61923818f8f856c0a726
7
- data.tar.gz: 408bcb8f9cde8c88ac6a0620a8a64c746e34723b582aa160b53687f7b5206a9cdcf97efb97eafcf6357cee6412ae3d0346a952e97b979f7c4eeffe23150d7ec4
6
+ metadata.gz: 01af679c600ae449256f30a3beb5f9993266e1e8bcd11e5ec8e635301a713c3175131e112540660d8adc67ae06470043eee48d1a3b1ecc69b2e436eb0010579c
7
+ data.tar.gz: 13a82671e440447415c22d930dd795d4f1d6e07ea9328078ed25c887890bbc27080f38fb5d33da6cb15dc8dac2789e3a930548a64d298b1ca44b510bb3978d5f
@@ -0,0 +1,56 @@
1
+ name: ci
2
+
3
+ on:
4
+ pull_request:
5
+ branches: '*'
6
+ push:
7
+ branches:
8
+ - master
9
+ - main
10
+ - 'release-*'
11
+
12
+ jobs:
13
+ mri:
14
+ strategy:
15
+ matrix:
16
+ os: [ubuntu, macos, windows]
17
+ ruby: ['2.4', '2.5', '2.6', '2.7', '3.0', '3.1']
18
+ runs-on: ${{ matrix.os }}-latest
19
+ steps:
20
+ - uses: actions/checkout@v2
21
+ - uses: ruby/setup-ruby@v1
22
+ with:
23
+ ruby-version: ${{ matrix.ruby }}
24
+ - run: bundle install
25
+ - run: bundle exec rake
26
+
27
+ jruby:
28
+ strategy:
29
+ matrix:
30
+ 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']
34
+ runs-on: ${{ matrix.os }}-latest
35
+ steps:
36
+ - uses: actions/checkout@v2
37
+ - uses: ruby/setup-ruby@v1
38
+ with:
39
+ ruby-version: ${{ matrix.ruby }}
40
+ - run: bundle install
41
+ - run: bundle exec rake
42
+
43
+ head-versions:
44
+ continue-on-error: true
45
+ strategy:
46
+ matrix:
47
+ os: [ubuntu]
48
+ ruby: ['ruby-head', 'jruby-head', 'truffleruby']
49
+ runs-on: ${{ matrix.os }}-latest
50
+ steps:
51
+ - uses: actions/checkout@v2
52
+ - uses: ruby/setup-ruby@v1
53
+ with:
54
+ ruby-version: ${{ matrix.ruby }}
55
+ - run: bundle install
56
+ - run: bundle exec rake || echo "failed, but ignore it"
data/ChangeLog CHANGED
@@ -1,3 +1,25 @@
1
+ 2022-01-20 version 1.4.3:
2
+
3
+ * Optimize serialization/deserialization of Symbols
4
+ * Support registering ext types for objects of subclasses of primitive types (like Hash)
5
+ * Add optimized_symbols_parsing option to Factory#register_type on MRI implementation
6
+ * Optimize to deduplicate Hash keys on JRuby
7
+ * Support JRuby 9.3 (and drop 9.1)
8
+
9
+ 2021-02-01 version 1.4.2:
10
+
11
+ * Add the required Ruby version (>= 2.4) to avoid compilation errors on older Ruby runtimes
12
+ * Drop the support of old Ruby versions explicitly (1.8, 1.9, 2.0, 2.1, 2.2, 2.3)
13
+
14
+ 2021-01-27 version 1.4.1:
15
+
16
+ * Bugfix about the wrong string encoding longer than 256 bytes (#200)
17
+
18
+ 2021-01-27 version 1.4.0:
19
+
20
+ * Introduce the optimization to use frozen/deduped keys for map objects
21
+ * Stop releasing fat gem (pre-built binaries) for mswin32 arch environments
22
+
1
23
  2020-02-05 version 1.3.3:
2
24
 
3
25
  * Hotfix release for Windows environments: 1.3.2 missed including binaries
data/README.md ADDED
@@ -0,0 +1,242 @@
1
+ # MessagePack
2
+
3
+ [MessagePack](http://msgpack.org) is an efficient binary serialization format.
4
+ It lets you exchange data among multiple languages like JSON but it's faster and smaller.
5
+ For example, small integers (like flags or error code) are encoded into a single byte,
6
+ and typical short strings only require an extra byte in addition to the strings themselves.
7
+
8
+ If you ever wished to use JSON for convenience (storing an image with metadata) but could
9
+ not for technical reasons (binary data, size, speed...), MessagePack is a perfect replacement.
10
+
11
+ require 'msgpack'
12
+ msg = [1,2,3].to_msgpack #=> "\x93\x01\x02\x03"
13
+ MessagePack.unpack(msg) #=> [1,2,3]
14
+
15
+ Use RubyGems to install:
16
+
17
+ gem install msgpack
18
+
19
+ or build msgpack-ruby and install:
20
+
21
+ bundle
22
+ rake
23
+ gem install --local pkg/msgpack
24
+
25
+
26
+ ## Use cases
27
+
28
+ * Create REST API returing MessagePack using Rails + [RABL](https://github.com/nesquena/rabl)
29
+ * Store objects efficiently serialized by msgpack on memcached or Redis
30
+ * In fact Redis supports msgpack in [EVAL-scripts](http://redis.io/commands/eval)
31
+ * Upload data in efficient format from mobile devices such as smartphones
32
+ * MessagePack works on iPhone/iPad and Android. See also [Objective-C](https://github.com/msgpack/msgpack-objectivec) and [Java](https://github.com/msgpack/msgpack-java) implementations
33
+ * Design a portable protocol to communicate with embedded devices
34
+ * Check also [Fluentd](http://fluentd.org/) which is a log collector which uses msgpack for the log format (they say it uses JSON but actually it's msgpack, which is compatible with JSON)
35
+ * Exchange objects between software components written in different languages
36
+ * You'll need a flexible but efficient format so that components exchange objects while keeping compatibility
37
+
38
+ ## Portability
39
+
40
+ MessagePack for Ruby should run on x86, ARM, PowerPC, SPARC and other CPU architectures.
41
+
42
+ And it works with MRI (CRuby) and Rubinius.
43
+ Patches to improve portability is highly welcomed.
44
+
45
+
46
+ ## Serializing objects
47
+
48
+ Use `MessagePack.pack` or `to_msgpack`:
49
+
50
+ ```ruby
51
+ require 'msgpack'
52
+ msg = MessagePack.pack(obj) # or
53
+ msg = obj.to_msgpack
54
+ ```
55
+
56
+ ### Streaming serialization
57
+
58
+ Packer provides advanced API to serialize objects in streaming style:
59
+
60
+ ```ruby
61
+ # serialize a 2-element array [e1, e2]
62
+ pk = MessagePack::Packer.new(io)
63
+ pk.write_array_header(2).write(e1).write(e2).flush
64
+ ```
65
+
66
+ See [API reference](http://ruby.msgpack.org/MessagePack/Packer.html) for details.
67
+
68
+ ## Deserializing objects
69
+
70
+ Use `MessagePack.unpack`:
71
+
72
+ ```ruby
73
+ require 'msgpack'
74
+ obj = MessagePack.unpack(msg)
75
+ ```
76
+
77
+ ### Streaming deserialization
78
+
79
+ Unpacker provides advanced API to deserialize objects in streaming style:
80
+
81
+ ```ruby
82
+ # deserialize objects from an IO
83
+ u = MessagePack::Unpacker.new(io)
84
+ u.each do |obj|
85
+ # ...
86
+ end
87
+ ```
88
+
89
+ or event-driven style which works well with EventMachine:
90
+
91
+ ```ruby
92
+ # event-driven deserialization
93
+ def on_read(data)
94
+ @u ||= MessagePack::Unpacker.new
95
+ @u.feed_each(data) {|obj|
96
+ # ...
97
+ }
98
+ end
99
+ ```
100
+
101
+ See [API reference](http://ruby.msgpack.org/MessagePack/Unpacker.html) for details.
102
+
103
+ ## Serializing and deserializing symbols
104
+
105
+ By default, symbols are serialized as strings:
106
+
107
+ ```ruby
108
+ packed = :symbol.to_msgpack # => "\xA6symbol"
109
+ MessagePack.unpack(packed) # => "symbol"
110
+ ```
111
+
112
+ This can be customized by registering an extension type for them:
113
+
114
+ ```ruby
115
+ MessagePack::DefaultFactory.register_type(0x00, Symbol)
116
+
117
+ # symbols now survive round trips
118
+ packed = :symbol.to_msgpack # => "\xc7\x06\x00symbol"
119
+ MessagePack.unpack(packed) # => :symbol
120
+ ```
121
+
122
+ The extension type for symbols is configurable like any other extension type.
123
+ For example, to customize how symbols are packed you can just redefine
124
+ Symbol#to_msgpack_ext. Doing this gives you an option to prevent symbols from
125
+ being serialized altogether by throwing an exception:
126
+
127
+ ```ruby
128
+ class Symbol
129
+ def to_msgpack_ext
130
+ raise "Serialization of symbols prohibited"
131
+ end
132
+ end
133
+
134
+ MessagePack::DefaultFactory.register_type(0x00, Symbol)
135
+
136
+ [1, :symbol, 'string'].to_msgpack # => RuntimeError: Serialization of symbols prohibited
137
+ ```
138
+
139
+ ## Serializing and deserializing Time instances
140
+
141
+ There are the timestamp extension type in MessagePack,
142
+ but it is not registered by default.
143
+
144
+ To map Ruby's Time to MessagePack's timestamp for the default factory:
145
+
146
+ ```ruby
147
+ MessagePack::DefaultFactory.register_type(
148
+ MessagePack::Timestamp::TYPE, # or just -1
149
+ Time,
150
+ packer: MessagePack::Time::Packer,
151
+ unpacker: MessagePack::Time::Unpacker
152
+ )
153
+ ```
154
+
155
+ See [API reference](http://ruby.msgpack.org/) for details.
156
+
157
+ ## Extension Types
158
+
159
+ Packer and Unpacker support [Extension types of MessagePack](https://github.com/msgpack/msgpack/blob/master/spec.md#types-extension-type).
160
+
161
+ ```ruby
162
+ # register how to serialize custom class at first
163
+ pk = MessagePack::Packer.new(io)
164
+ pk.register_type(0x01, MyClass1, :to_msgpack_ext) # equal to pk.register_type(0x01, MyClass)
165
+ pk.register_type(0x02, MyClass2){|obj| obj.how_to_serialize() } # blocks also available
166
+
167
+ # almost same API for unpacker
168
+ uk = MessagePack::Unpacker.new()
169
+ uk.register_type(0x01, MyClass1, :from_msgpack_ext)
170
+ uk.register_type(0x02){|data| MyClass2.create_from_serialized_data(data) }
171
+ ```
172
+
173
+ `MessagePack::Factory` is to create packer and unpacker which have same extension types.
174
+
175
+ ```ruby
176
+ factory = MessagePack::Factory.new
177
+ factory.register_type(0x01, MyClass1) # same with next line
178
+ factory.register_type(0x01, MyClass1, packer: :to_msgpack_ext, unpacker: :from_msgpack_ext)
179
+ pk = factory.packer(options_for_packer)
180
+ uk = factory.unpacker(options_for_unpacker)
181
+ ```
182
+
183
+ For `MessagePack.pack` and `MessagePack.unpack`, default packer/unpacker refer `MessagePack::DefaultFactory`. Call `MessagePack::DefaultFactory.register_type` to enable types process globally.
184
+
185
+ ```ruby
186
+ MessagePack::DefaultFactory.register_type(0x03, MyClass3)
187
+ MessagePack.unpack(data_with_ext_typeid_03) #=> MyClass3 instance
188
+ ```
189
+
190
+ ## Buffer API
191
+
192
+ MessagePack for Ruby provides a buffer API so that you can read or write data by hand, not via Packer or Unpacker API.
193
+
194
+ This [MessagePack::Buffer](http://ruby.msgpack.org/MessagePack/Buffer.html) is backed with a fixed-length shared memory pool which is very fast for small data (<= 4KB),
195
+ and has zero-copy capability which significantly affects performance to handle large binary data.
196
+
197
+ ## How to build and run tests
198
+
199
+ Before building msgpack, you need to install bundler and dependencies.
200
+
201
+ gem install bundler
202
+ bundle install
203
+
204
+ Then, you can run the tasks as follows:
205
+
206
+ ### Build
207
+
208
+ bundle exec rake build
209
+
210
+ ### Run tests
211
+
212
+ bundle exec rake spec
213
+
214
+ ### Generating docs
215
+
216
+ bundle exec rake doc
217
+
218
+ ## How to build -java rubygems
219
+
220
+ To build -java gems for JRuby, run:
221
+
222
+ rake build:java
223
+
224
+ If this directory has Gemfile.lock (generated with MRI), remove it beforehand.
225
+
226
+ ## Updating documents
227
+
228
+ Online documents (http://ruby.msgpack.org) is generated from gh-pages branch.
229
+ Following commands update documents in gh-pages branch:
230
+
231
+ bundle exec rake doc
232
+ git checkout gh-pages
233
+ cp doc/* ./ -a
234
+
235
+ ## Copyright
236
+
237
+ * Author
238
+ * Sadayuki Furuhashi <frsyuki@gmail.com>
239
+ * Copyright
240
+ * Copyright (c) 2008-2015 Sadayuki Furuhashi
241
+ * License
242
+ * Apache License, Version 2.0
data/Rakefile CHANGED
@@ -34,8 +34,8 @@ 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.6'
38
- ext.target_version = '1.6'
37
+ ext.source_version = '1.8'
38
+ ext.target_version = '1.8'
39
39
  end
40
40
  else
41
41
  require 'rake/extensiontask'
@@ -64,13 +64,6 @@ end
64
64
  namespace :build do
65
65
  desc 'Build gem for JRuby after cleaning'
66
66
  task :java => [:clean, :spec, :build]
67
-
68
- desc 'Build gems for Windows per rake-compiler-dock'
69
- task :windows do
70
- require 'rake_compiler_dock'
71
- # See RUBY_CC_VERSION in https://github.com/rake-compiler/rake-compiler-dock/blob/master/Dockerfile
72
- RakeCompilerDock.sh 'bundle && gem i json && rake cross native gem RUBY_CC_VERSION=2.2.2:2.3.0:2.4.0:2.5.0:2.6.0:2.7.0'
73
- end
74
67
  end
75
68
 
76
69
  CLEAN.include('lib/msgpack/msgpack.*')
@@ -75,6 +75,7 @@ module MessagePack
75
75
  #
76
76
  # * *:packer* specify symbol or proc object for packer
77
77
  # * *:unpacker* specify symbol or proc object for unpacker
78
+ # * *:optimized_symbols_parsing* specify true to use the optimized symbols parsing (not supported on JRuby now)
78
79
  #
79
80
  def register_type(type, klass, options={})
80
81
  end
@@ -21,6 +21,7 @@ import org.jcodings.Encoding;
21
21
 
22
22
  @JRubyClass(name="MessagePack::Buffer")
23
23
  public class Buffer extends RubyObject {
24
+ private static final long serialVersionUID = 8441244627425629412L;
24
25
  private IRubyObject io;
25
26
  private ByteBuffer buffer;
26
27
  private boolean writeMode;
@@ -49,7 +50,7 @@ public class Buffer extends RubyObject {
49
50
  }
50
51
  this.buffer = ByteBuffer.allocate(CACHE_LINE_SIZE - ARRAY_HEADER_SIZE);
51
52
  this.writeMode = true;
52
- this.binaryEncoding = ctx.getRuntime().getEncodingService().getAscii8bitEncoding();
53
+ this.binaryEncoding = ctx.runtime.getEncodingService().getAscii8bitEncoding();
53
54
  return this;
54
55
  }
55
56
 
@@ -87,17 +88,17 @@ public class Buffer extends RubyObject {
87
88
  writeMode = true;
88
89
  }
89
90
  buffer.clear();
90
- return ctx.getRuntime().getNil();
91
+ return ctx.runtime.getNil();
91
92
  }
92
93
 
93
94
  @JRubyMethod(name = "size")
94
95
  public IRubyObject size(ThreadContext ctx) {
95
- return ctx.getRuntime().newFixnum(rawSize());
96
+ return ctx.runtime.newFixnum(rawSize());
96
97
  }
97
98
 
98
99
  @JRubyMethod(name = "empty?")
99
100
  public IRubyObject isEmpty(ThreadContext ctx) {
100
- return rawSize() == 0 ? ctx.getRuntime().getTrue() : ctx.getRuntime().getFalse();
101
+ return rawSize() == 0 ? ctx.runtime.getTrue() : ctx.runtime.getFalse();
101
102
  }
102
103
 
103
104
  private IRubyObject bufferWrite(ThreadContext ctx, IRubyObject str) {
@@ -105,7 +106,7 @@ public class Buffer extends RubyObject {
105
106
  int length = bytes.length();
106
107
  ensureRemainingCapacity(length);
107
108
  buffer.put(bytes.unsafeBytes(), bytes.begin(), length);
108
- return ctx.getRuntime().newFixnum(length);
109
+ return ctx.runtime.newFixnum(length);
109
110
 
110
111
  }
111
112
 
@@ -131,19 +132,19 @@ public class Buffer extends RubyObject {
131
132
  length = (int) args[0].convertToInteger().getLongValue();
132
133
  }
133
134
  if (raiseOnUnderflow && rawSize() < length) {
134
- throw ctx.getRuntime().newEOFError();
135
+ throw ctx.runtime.newEOFError();
135
136
  }
136
137
  int readLength = Math.min(length, rawSize());
137
138
  if (readLength == 0 && length > 0) {
138
- return ctx.getRuntime().getNil();
139
+ return ctx.runtime.getNil();
139
140
  } else if (readLength == 0) {
140
- return ctx.getRuntime().newString();
141
+ return ctx.runtime.newString();
141
142
  } else {
142
143
  ensureReadMode();
143
144
  byte[] bytes = new byte[readLength];
144
145
  buffer.get(bytes);
145
146
  ByteList byteList = new ByteList(bytes, binaryEncoding);
146
- return ctx.getRuntime().newString(byteList);
147
+ return ctx.runtime.newString(byteList);
147
148
  }
148
149
  }
149
150
 
@@ -161,12 +162,12 @@ public class Buffer extends RubyObject {
161
162
  feed(ctx);
162
163
  int length = (int) _length.convertToInteger().getLongValue();
163
164
  if (raiseOnUnderflow && rawSize() < length) {
164
- throw ctx.getRuntime().newEOFError();
165
+ throw ctx.runtime.newEOFError();
165
166
  }
166
167
  ensureReadMode();
167
168
  int skipLength = Math.min(length, rawSize());
168
169
  buffer.position(buffer.position() + skipLength);
169
- return ctx.getRuntime().newFixnum(skipLength);
170
+ return ctx.runtime.newFixnum(skipLength);
170
171
  }
171
172
 
172
173
  @JRubyMethod(name = "skip")
@@ -188,23 +189,23 @@ public class Buffer extends RubyObject {
188
189
  ensureReadMode();
189
190
  int length = buffer.limit() - buffer.position();
190
191
  ByteList str = new ByteList(buffer.array(), buffer.position(), length, binaryEncoding, true);
191
- return ctx.getRuntime().newString(str);
192
+ return ctx.runtime.newString(str);
192
193
  }
193
194
 
194
195
  @JRubyMethod(name = "to_a")
195
196
  public IRubyObject toA(ThreadContext ctx) {
196
- return ctx.getRuntime().newArray(toS(ctx));
197
+ return ctx.runtime.newArray(toS(ctx));
197
198
  }
198
199
 
199
200
  @JRubyMethod(name = "io")
200
201
  public IRubyObject getIo(ThreadContext ctx) {
201
- return io == null ? ctx.getRuntime().getNil() : io;
202
+ return io == null ? ctx.runtime.getNil() : io;
202
203
  }
203
204
 
204
205
  @JRubyMethod(name = "flush")
205
206
  public IRubyObject flush(ThreadContext ctx) {
206
207
  if (io == null) {
207
- return ctx.getRuntime().getNil();
208
+ return ctx.runtime.getNil();
208
209
  } else {
209
210
  return io.callMethod(ctx, "flush");
210
211
  }
@@ -213,7 +214,7 @@ public class Buffer extends RubyObject {
213
214
  @JRubyMethod(name = "close")
214
215
  public IRubyObject close(ThreadContext ctx) {
215
216
  if (io == null) {
216
- return ctx.getRuntime().getNil();
217
+ return ctx.runtime.getNil();
217
218
  } else {
218
219
  return io.callMethod(ctx, "close");
219
220
  }
@@ -38,36 +38,38 @@ public class Decoder implements Iterator<IRubyObject> {
38
38
  private ExtensionRegistry registry;
39
39
  private ByteBuffer buffer;
40
40
  private boolean symbolizeKeys;
41
+ private boolean freeze;
41
42
  private boolean allowUnknownExt;
42
43
 
43
44
  public Decoder(Ruby runtime) {
44
- this(runtime, null, new byte[] {}, 0, 0, false, false);
45
+ this(runtime, null, new byte[] {}, 0, 0, false, false, false);
45
46
  }
46
47
 
47
48
  public Decoder(Ruby runtime, ExtensionRegistry registry) {
48
- this(runtime, registry, new byte[] {}, 0, 0, false, false);
49
+ this(runtime, registry, new byte[] {}, 0, 0, false, false, false);
49
50
  }
50
51
 
51
52
  public Decoder(Ruby runtime, byte[] bytes) {
52
- this(runtime, null, bytes, 0, bytes.length, false, false);
53
+ this(runtime, null, bytes, 0, bytes.length, false, false, false);
53
54
  }
54
55
 
55
56
  public Decoder(Ruby runtime, ExtensionRegistry registry, byte[] bytes) {
56
- this(runtime, registry, bytes, 0, bytes.length, false, false);
57
+ this(runtime, registry, bytes, 0, bytes.length, false, false, false);
57
58
  }
58
59
 
59
- public Decoder(Ruby runtime, ExtensionRegistry registry, byte[] bytes, boolean symbolizeKeys, boolean allowUnknownExt) {
60
- this(runtime, registry, bytes, 0, bytes.length, symbolizeKeys, allowUnknownExt);
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
62
  }
62
63
 
63
64
  public Decoder(Ruby runtime, ExtensionRegistry registry, byte[] bytes, int offset, int length) {
64
- this(runtime, registry, bytes, offset, length, false, false);
65
+ this(runtime, registry, bytes, offset, length, false, false, false);
65
66
  }
66
67
 
67
- public Decoder(Ruby runtime, ExtensionRegistry registry, byte[] bytes, int offset, int length, boolean symbolizeKeys, boolean allowUnknownExt) {
68
+ public Decoder(Ruby runtime, ExtensionRegistry registry, byte[] bytes, int offset, int length, boolean symbolizeKeys, boolean freeze, boolean allowUnknownExt) {
68
69
  this.runtime = runtime;
69
70
  this.registry = registry;
70
71
  this.symbolizeKeys = symbolizeKeys;
72
+ this.freeze = freeze;
71
73
  this.allowUnknownExt = allowUnknownExt;
72
74
  this.binaryEncoding = runtime.getEncodingService().getAscii8bitEncoding();
73
75
  this.utf8Encoding = UTF8Encoding.INSTANCE;
@@ -118,7 +120,11 @@ public class Decoder implements Iterator<IRubyObject> {
118
120
  private IRubyObject consumeString(int size, Encoding encoding) {
119
121
  byte[] bytes = readBytes(size);
120
122
  ByteList byteList = new ByteList(bytes, encoding);
121
- return runtime.newString(byteList);
123
+ RubyString string = runtime.newString(byteList);
124
+ if (this.freeze) {
125
+ string = runtime.freezeAndDedupString(string);
126
+ }
127
+ return string;
122
128
  }
123
129
 
124
130
  private IRubyObject consumeArray(int size) {
@@ -133,9 +139,14 @@ public class Decoder implements Iterator<IRubyObject> {
133
139
  RubyHash hash = RubyHash.newHash(runtime);
134
140
  for (int i = 0; i < size; i++) {
135
141
  IRubyObject key = next();
136
- if (this.symbolizeKeys && key instanceof RubyString) {
142
+ if (key instanceof RubyString) {
143
+ if (this.symbolizeKeys) {
137
144
  key = ((RubyString) key).intern();
145
+ } else {
146
+ key = runtime.freezeAndDedupString((RubyString) key);
147
+ }
138
148
  }
149
+
139
150
  hash.fastASet(key, next());
140
151
  }
141
152
  return hash;
@@ -220,6 +231,14 @@ public class Decoder implements Iterator<IRubyObject> {
220
231
 
221
232
  @Override
222
233
  public IRubyObject next() {
234
+ IRubyObject next = consumeNext();
235
+ if (freeze) {
236
+ next.setFrozen(true);
237
+ }
238
+ return next;
239
+ }
240
+
241
+ private IRubyObject consumeNext() {
223
242
  int position = buffer.position();
224
243
  try {
225
244
  byte b = buffer.get();
@@ -119,7 +119,9 @@ public class Encoder {
119
119
  } else if (object instanceof RubyFloat) {
120
120
  appendFloat((RubyFloat) object);
121
121
  } else if (object instanceof RubyString) {
122
- appendString((RubyString) object);
122
+ if (object.getType() == runtime.getString() || !tryAppendWithExtTypeLookup(object)) {
123
+ appendString((RubyString) object);
124
+ }
123
125
  } else if (object instanceof RubySymbol) {
124
126
  if (hasSymbolExtType) {
125
127
  appendOther(object, destination);
@@ -127,9 +129,13 @@ public class Encoder {
127
129
  appendString(((RubySymbol) object).asString());
128
130
  }
129
131
  } else if (object instanceof RubyArray) {
130
- appendArray((RubyArray) object);
132
+ if (object.getType() == runtime.getArray() || !tryAppendWithExtTypeLookup(object)) {
133
+ appendArray((RubyArray) object);
134
+ }
131
135
  } else if (object instanceof RubyHash) {
132
- appendHash((RubyHash) object);
136
+ if (object.getType() == runtime.getHash() || !tryAppendWithExtTypeLookup(object)) {
137
+ appendHash((RubyHash) object);
138
+ }
133
139
  } else if (object instanceof ExtensionValue) {
134
140
  appendExtensionValue((ExtensionValue) object);
135
141
  } else {
@@ -153,7 +159,7 @@ public class Encoder {
153
159
  }
154
160
 
155
161
  private void appendInteger(RubyInteger object) {
156
- long value = ((RubyInteger) object).getLongValue();
162
+ long value = object.getLongValue();
157
163
  if (value < 0) {
158
164
  if (value < Short.MIN_VALUE) {
159
165
  if (value < Integer.MIN_VALUE) {
@@ -241,7 +247,7 @@ public class Encoder {
241
247
  } else {
242
248
  ensureRemainingCapacity(5 + length);
243
249
  buffer.put(binary ? BIN32 : STR32);
244
- buffer.putInt((int) length);
250
+ buffer.putInt(length);
245
251
  }
246
252
  }
247
253
 
@@ -249,7 +255,7 @@ public class Encoder {
249
255
  Encoding encoding = object.getEncoding();
250
256
  boolean binary = !compatibilityMode && encoding == binaryEncoding;
251
257
  if (encoding != utf8Encoding && encoding != binaryEncoding) {
252
- object = (RubyString) ((RubyString) object).encode(runtime.getCurrentContext(), runtime.getEncodingService().getEncoding(utf8Encoding));
258
+ object = (RubyString)(object).encode(runtime.getCurrentContext(), runtime.getEncodingService().getEncoding(utf8Encoding));
253
259
  }
254
260
  ByteList bytes = object.getByteList();
255
261
  int length = bytes.length();
@@ -257,12 +263,12 @@ public class Encoder {
257
263
  buffer.put(bytes.unsafeBytes(), bytes.begin(), length);
258
264
  }
259
265
 
260
- private void appendArray(RubyArray object) {
266
+ private void appendArray(RubyArray<?> object) {
261
267
  appendArrayHeader(object);
262
268
  appendArrayElements(object);
263
269
  }
264
270
 
265
- private void appendArrayHeader(RubyArray object) {
271
+ private void appendArrayHeader(RubyArray<?> object) {
266
272
  appendArrayHeader(object.size());
267
273
  }
268
274
 
@@ -281,7 +287,7 @@ public class Encoder {
281
287
  }
282
288
  }
283
289
 
284
- private void appendArrayElements(RubyArray object) {
290
+ private void appendArrayElements(RubyArray<?> object) {
285
291
  int size = object.size();
286
292
  for (int i = 0; i < size; i++) {
287
293
  appendObject(object.eltOk(i));
@@ -383,7 +389,7 @@ public class Encoder {
383
389
  appendExt((int) type, payloadBytes);
384
390
  }
385
391
 
386
- private void appendOther(IRubyObject object, IRubyObject destination) {
392
+ private boolean tryAppendWithExtTypeLookup(IRubyObject object) {
387
393
  if (registry != null) {
388
394
  RubyModule lookupClass;
389
395
 
@@ -398,10 +404,14 @@ public class Encoder {
398
404
  RubyString bytes = pair[0].callMethod(runtime.getCurrentContext(), "call", object).asString();
399
405
  int type = (int) ((RubyFixnum) pair[1]).getLongValue();
400
406
  appendExt(type, bytes.getByteList());
401
- return;
407
+ return true;
402
408
  }
403
409
  }
404
- appendCustom(object, destination);
410
+ return false;
411
+ }
412
+
413
+ private void appendOther(IRubyObject object, IRubyObject destination) {
414
+ if (!tryAppendWithExtTypeLookup(object)) { appendCustom(object, destination); }
405
415
  }
406
416
 
407
417
  private void appendCustom(IRubyObject object, IRubyObject destination) {