Skip to content

Commit 540fdb9

Browse files
committed
Merge pull request #91 from msgpack/ext-type-jruby
add full spec ext type support to JRuby implementation
2 parents 6014ece + 654de1b commit 540fdb9

22 files changed

Lines changed: 1210 additions & 637 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Gemfile*
1111
pkg
1212
test/debug.log
1313
*~
14+
*.swp
1415
/rdoc
1516
tmp
1617
.classpath

ext/java/org/msgpack/jruby/Decoder.java

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.jruby.RubyClass;
1313
import org.jruby.RubyBignum;
1414
import org.jruby.RubyString;
15+
import org.jruby.RubyArray;
1516
import org.jruby.RubyHash;
1617
import org.jruby.exceptions.RaiseException;
1718
import org.jruby.runtime.builtin.IRubyObject;
@@ -29,33 +30,58 @@ public class Decoder implements Iterator<IRubyObject> {
2930
private final Encoding utf8Encoding;
3031
private final RubyClass unpackErrorClass;
3132
private final RubyClass underflowErrorClass;
33+
private final RubyClass malformedFormatErrorClass;
34+
private final RubyClass stackErrorClass;
3235
private final RubyClass unexpectedTypeErrorClass;
36+
private final RubyClass unknownExtTypeErrorClass;
3337

38+
private ExtensionRegistry registry;
3439
private ByteBuffer buffer;
3540
private boolean symbolizeKeys;
41+
private boolean allowUnknownExt;
3642

3743
public Decoder(Ruby runtime) {
38-
this(runtime, new byte[] {}, 0, 0);
44+
this(runtime, null, new byte[] {}, 0, 0, false, false);
45+
}
46+
47+
public Decoder(Ruby runtime, ExtensionRegistry registry) {
48+
this(runtime, registry, new byte[] {}, 0, 0, false, false);
3949
}
4050

4151
public Decoder(Ruby runtime, byte[] bytes) {
42-
this(runtime, bytes, 0, bytes.length);
52+
this(runtime, null, bytes, 0, bytes.length, false, false);
53+
}
54+
55+
public Decoder(Ruby runtime, ExtensionRegistry registry, byte[] bytes) {
56+
this(runtime, registry, bytes, 0, bytes.length, false, false);
57+
}
58+
59+
public Decoder(Ruby runtime, ExtensionRegistry registry, byte[] bytes, boolean symbolizeKeys, boolean allowUnknownExt) {
60+
this(runtime, registry, bytes, 0, bytes.length, symbolizeKeys, allowUnknownExt);
61+
}
62+
63+
public Decoder(Ruby runtime, ExtensionRegistry registry, byte[] bytes, int offset, int length) {
64+
this(runtime, registry, bytes, offset, length, false, false);
4365
}
4466

45-
public Decoder(Ruby runtime, byte[] bytes, int offset, int length) {
67+
public Decoder(Ruby runtime, ExtensionRegistry registry, byte[] bytes, int offset, int length, boolean symbolizeKeys, boolean allowUnknownExt) {
4668
this.runtime = runtime;
69+
this.registry = registry;
70+
this.symbolizeKeys = symbolizeKeys;
71+
this.allowUnknownExt = allowUnknownExt;
4772
this.binaryEncoding = runtime.getEncodingService().getAscii8bitEncoding();
4873
this.utf8Encoding = UTF8Encoding.INSTANCE;
4974
this.unpackErrorClass = runtime.getModule("MessagePack").getClass("UnpackError");
5075
this.underflowErrorClass = runtime.getModule("MessagePack").getClass("UnderflowError");
76+
this.malformedFormatErrorClass = runtime.getModule("MessagePack").getClass("MalformedFormatError");
77+
this.stackErrorClass = runtime.getModule("MessagePack").getClass("StackError");
5178
this.unexpectedTypeErrorClass = runtime.getModule("MessagePack").getClass("UnexpectedTypeError");
79+
this.unknownExtTypeErrorClass = runtime.getModule("MessagePack").getClass("UnknownExtTypeError");
80+
this.symbolizeKeys = symbolizeKeys;
81+
this.allowUnknownExt = allowUnknownExt;
5282
feed(bytes, offset, length);
5383
}
5484

55-
public void symbolizeKeys(boolean symbolize) {
56-
this.symbolizeKeys = symbolize;
57-
}
58-
5985
public void feed(byte[] bytes) {
6086
feed(bytes, 0, bytes.length);
6187
}
@@ -73,7 +99,7 @@ public void feed(byte[] bytes, int offset, int length) {
7399
}
74100

75101
public void reset() {
76-
buffer.rewind();
102+
buffer = null;
77103
}
78104

79105
public int offset() {
@@ -118,7 +144,20 @@ private IRubyObject consumeHash(int size) {
118144
private IRubyObject consumeExtension(int size) {
119145
int type = buffer.get();
120146
byte[] payload = readBytes(size);
121-
return ExtensionValue.newExtensionValue(runtime, type, payload);
147+
148+
if (registry != null) {
149+
IRubyObject proc = registry.lookupUnpackerByTypeId(type);
150+
if (proc != null) {
151+
ByteList byteList = new ByteList(payload, runtime.getEncodingService().getAscii8bitEncoding());
152+
return proc.callMethod(runtime.getCurrentContext(), "call", runtime.newString(byteList));
153+
}
154+
}
155+
156+
if (this.allowUnknownExt) {
157+
return ExtensionValue.newExtensionValue(runtime, type, payload);
158+
}
159+
160+
throw runtime.newRaiseException(unknownExtTypeErrorClass, "unexpected extension type");
122161
}
123162

124163
private byte[] readBytes(int size) {
@@ -142,11 +181,11 @@ public IRubyObject read_array_header() {
142181
try {
143182
byte b = buffer.get();
144183
if ((b & 0xf0) == 0x90) {
145-
return runtime.newFixnum(b & 0x0f);
184+
return runtime.newFixnum(b & 0x0f);
146185
} else if (b == ARY16) {
147-
return runtime.newFixnum(buffer.getShort() & 0xffff);
186+
return runtime.newFixnum(buffer.getShort() & 0xffff);
148187
} else if (b == ARY32) {
149-
return runtime.newFixnum(buffer.getInt());
188+
return runtime.newFixnum(buffer.getInt());
150189
}
151190
throw runtime.newRaiseException(unexpectedTypeErrorClass, "unexpected type");
152191
} catch (RaiseException re) {
@@ -163,11 +202,11 @@ public IRubyObject read_map_header() {
163202
try {
164203
byte b = buffer.get();
165204
if ((b & 0xf0) == 0x80) {
166-
return runtime.newFixnum(b & 0x0f);
205+
return runtime.newFixnum(b & 0x0f);
167206
} else if (b == MAP16) {
168-
return runtime.newFixnum(buffer.getShort() & 0xffff);
207+
return runtime.newFixnum(buffer.getShort() & 0xffff);
169208
} else if (b == MAP32) {
170-
return runtime.newFixnum(buffer.getInt());
209+
return runtime.newFixnum(buffer.getInt());
171210
}
172211
throw runtime.newRaiseException(unexpectedTypeErrorClass, "unexpected type");
173212
} catch (RaiseException re) {
@@ -233,7 +272,7 @@ public IRubyObject next() {
233272
default: return runtime.newFixnum(b);
234273
}
235274
buffer.position(position);
236-
throw runtime.newRaiseException(unpackErrorClass, "Illegal byte sequence");
275+
throw runtime.newRaiseException(malformedFormatErrorClass, "Illegal byte sequence");
237276
} catch (RaiseException re) {
238277
buffer.position(position);
239278
throw re;

ext/java/org/msgpack/jruby/Encoder.java

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,21 @@ public class Encoder {
3434
private final Encoding binaryEncoding;
3535
private final Encoding utf8Encoding;
3636
private final boolean compatibilityMode;
37+
private final ExtensionRegistry registry;
3738

3839
private ByteBuffer buffer;
3940

40-
public Encoder(Ruby runtime, boolean compatibilityMode) {
41+
public Encoder(Ruby runtime, boolean compatibilityMode, ExtensionRegistry registry) {
4142
this.runtime = runtime;
4243
this.buffer = ByteBuffer.allocate(CACHE_LINE_SIZE - ARRAY_HEADER_SIZE);
4344
this.binaryEncoding = runtime.getEncodingService().getAscii8bitEncoding();
4445
this.utf8Encoding = UTF8Encoding.INSTANCE;
4546
this.compatibilityMode = compatibilityMode;
47+
this.registry = registry;
48+
}
49+
50+
public boolean isCompatibilityMode() {
51+
return compatibilityMode;
4652
}
4753

4854
private void ensureRemainingCapacity(int c) {
@@ -107,7 +113,7 @@ private void appendObject(IRubyObject object, IRubyObject destination) {
107113
} else if (object instanceof ExtensionValue) {
108114
appendExtensionValue((ExtensionValue) object);
109115
} else {
110-
appendCustom(object, destination);
116+
appendOther(object, destination);
111117
}
112118
}
113119

@@ -295,12 +301,7 @@ public void visit(IRubyObject key, IRubyObject value) {
295301
}
296302
}
297303

298-
private void appendExtensionValue(ExtensionValue object) {
299-
long type = ((RubyFixnum)object.get_type()).getLongValue();
300-
if (type < -128 || type > 127) {
301-
throw object.getRuntime().newRangeError(String.format("integer %d too big to convert to `signed char'", type));
302-
}
303-
ByteList payloadBytes = ((RubyString)object.payload()).getByteList();
304+
private void appendExt(int type, ByteList payloadBytes) {
304305
int payloadSize = payloadBytes.length();
305306
int outputSize = 0;
306307
boolean fixSize = payloadSize == 1 || payloadSize == 2 || payloadSize == 4 || payloadSize == 8 || payloadSize == 16;
@@ -338,6 +339,28 @@ private void appendExtensionValue(ExtensionValue object) {
338339
buffer.put(payloadBytes.unsafeBytes(), payloadBytes.begin(), payloadSize);
339340
}
340341

342+
private void appendExtensionValue(ExtensionValue object) {
343+
long type = ((RubyFixnum)object.get_type()).getLongValue();
344+
if (type < -128 || type > 127) {
345+
throw object.getRuntime().newRangeError(String.format("integer %d too big to convert to `signed char'", type));
346+
}
347+
ByteList payloadBytes = ((RubyString)object.payload()).getByteList();
348+
appendExt((int) type, payloadBytes);
349+
}
350+
351+
private void appendOther(IRubyObject object, IRubyObject destination) {
352+
if (registry != null) {
353+
IRubyObject[] pair = registry.lookupPackerByClass(object.getType());
354+
if (pair != null) {
355+
RubyString bytes = pair[0].callMethod(runtime.getCurrentContext(), "call", object).asString();
356+
int type = (int) ((RubyFixnum) pair[1]).getLongValue();
357+
appendExt(type, bytes.getByteList());
358+
return;
359+
}
360+
}
361+
appendCustom(object, destination);
362+
}
363+
341364
private void appendCustom(IRubyObject object, IRubyObject destination) {
342365
if (destination == null) {
343366
IRubyObject result = object.callMethod(runtime.getCurrentContext(), "to_msgpack");
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package org.msgpack.jruby;
2+
3+
import org.jruby.Ruby;
4+
import org.jruby.RubyHash;
5+
import org.jruby.RubyArray;
6+
import org.jruby.RubyClass;
7+
import org.jruby.RubyFixnum;
8+
import org.jruby.runtime.ThreadContext;
9+
import org.jruby.runtime.builtin.IRubyObject;
10+
11+
import java.util.Map;
12+
import java.util.HashMap;
13+
14+
public class ExtensionRegistry {
15+
private final Map<RubyClass, ExtensionEntry> extensionsByClass;
16+
private final Map<RubyClass, ExtensionEntry> extensionsByAncestor;
17+
private final ExtensionEntry[] extensionsByTypeId;
18+
19+
public ExtensionRegistry() {
20+
this(new HashMap<RubyClass, ExtensionEntry>());
21+
}
22+
23+
private ExtensionRegistry(Map<RubyClass, ExtensionEntry> extensionsByClass) {
24+
this.extensionsByClass = new HashMap<RubyClass, ExtensionEntry>(extensionsByClass);
25+
this.extensionsByAncestor = new HashMap<RubyClass, ExtensionEntry>();
26+
this.extensionsByTypeId = new ExtensionEntry[256];
27+
for (ExtensionEntry entry : extensionsByClass.values()) {
28+
if (entry.hasUnpacker()) {
29+
extensionsByTypeId[entry.getTypeId() + 128] = entry;
30+
}
31+
}
32+
}
33+
34+
public ExtensionRegistry dup() {
35+
return new ExtensionRegistry(extensionsByClass);
36+
}
37+
38+
public IRubyObject toInternalPackerRegistry(ThreadContext ctx) {
39+
RubyHash hash = RubyHash.newHash(ctx.getRuntime());
40+
for (RubyClass extensionClass : extensionsByClass.keySet()) {
41+
ExtensionEntry entry = extensionsByClass.get(extensionClass);
42+
if (entry.hasPacker()) {
43+
hash.put(extensionClass, entry.toPackerTuple(ctx));
44+
}
45+
}
46+
return hash;
47+
}
48+
49+
public IRubyObject toInternalUnpackerRegistry(ThreadContext ctx) {
50+
RubyHash hash = RubyHash.newHash(ctx.getRuntime());
51+
for (int typeIdIndex = 0 ; typeIdIndex < 256 ; typeIdIndex++) {
52+
ExtensionEntry entry = extensionsByTypeId[typeIdIndex];
53+
if (entry != null && entry.hasUnpacker()) {
54+
IRubyObject typeId = RubyFixnum.newFixnum(ctx.getRuntime(), typeIdIndex - 128);
55+
hash.put(typeId, entry.toUnpackerTuple(ctx));
56+
}
57+
}
58+
return hash;
59+
}
60+
61+
public void put(RubyClass cls, int typeId, IRubyObject packerProc, IRubyObject packerArg, IRubyObject unpackerProc, IRubyObject unpackerArg) {
62+
ExtensionEntry entry = new ExtensionEntry(cls, typeId, packerProc, packerArg, unpackerProc, unpackerArg);
63+
extensionsByClass.put(cls, entry);
64+
extensionsByTypeId[typeId + 128] = entry;
65+
extensionsByAncestor.clear();
66+
}
67+
68+
public IRubyObject lookupUnpackerByTypeId(int typeId) {
69+
ExtensionEntry e = extensionsByTypeId[typeId + 128];
70+
if (e != null && e.hasUnpacker()) {
71+
return e.getUnpackerProc();
72+
} else {
73+
return null;
74+
}
75+
}
76+
77+
public IRubyObject[] lookupPackerByClass(RubyClass cls) {
78+
ExtensionEntry e = extensionsByClass.get(cls);
79+
if (e == null) {
80+
e = extensionsByAncestor.get(cls);
81+
}
82+
if (e == null) {
83+
e = findEntryByClassOrAncestor(cls);
84+
if (e != null) {
85+
extensionsByAncestor.put(e.getExtensionClass(), e);
86+
}
87+
}
88+
if (e != null && e.hasPacker()) {
89+
return e.toPackerProcTypeIdPair(cls.getRuntime().getCurrentContext());
90+
} else {
91+
return null;
92+
}
93+
}
94+
95+
private ExtensionEntry findEntryByClassOrAncestor(final RubyClass cls) {
96+
ThreadContext ctx = cls.getRuntime().getCurrentContext();
97+
for (RubyClass extensionClass : extensionsByClass.keySet()) {
98+
RubyArray ancestors = (RubyArray) cls.callMethod(ctx, "ancestors");
99+
if (ancestors.callMethod(ctx, "include?", extensionClass).isTrue()) {
100+
return extensionsByClass.get(extensionClass);
101+
}
102+
}
103+
return null;
104+
}
105+
106+
private static class ExtensionEntry {
107+
private final RubyClass cls;
108+
private final int typeId;
109+
private final IRubyObject packerProc;
110+
private final IRubyObject packerArg;
111+
private final IRubyObject unpackerProc;
112+
private final IRubyObject unpackerArg;
113+
114+
public ExtensionEntry(RubyClass cls, int typeId, IRubyObject packerProc, IRubyObject packerArg, IRubyObject unpackerProc, IRubyObject unpackerArg) {
115+
this.cls = cls;
116+
this.typeId = typeId;
117+
this.packerProc = packerProc;
118+
this.packerArg = packerArg;
119+
this.unpackerProc = unpackerProc;
120+
this.unpackerArg = unpackerArg;
121+
}
122+
123+
public RubyClass getExtensionClass() {
124+
return cls;
125+
}
126+
127+
public int getTypeId() {
128+
return typeId;
129+
}
130+
131+
public boolean hasPacker() {
132+
return packerProc != null;
133+
}
134+
135+
public boolean hasUnpacker() {
136+
return unpackerProc != null;
137+
}
138+
139+
public IRubyObject getPackerProc() {
140+
return packerProc;
141+
}
142+
143+
public IRubyObject getUnpackerProc() {
144+
return unpackerProc;
145+
}
146+
147+
public RubyArray toPackerTuple(ThreadContext ctx) {
148+
return RubyArray.newArray(ctx.getRuntime(), new IRubyObject[] {RubyFixnum.newFixnum(ctx.getRuntime(), typeId), packerProc, packerArg});
149+
}
150+
151+
public RubyArray toUnpackerTuple(ThreadContext ctx) {
152+
return RubyArray.newArray(ctx.getRuntime(), new IRubyObject[] {cls, unpackerProc, unpackerArg});
153+
}
154+
155+
public IRubyObject[] toPackerProcTypeIdPair(ThreadContext ctx) {
156+
return new IRubyObject[] {packerProc, RubyFixnum.newFixnum(ctx.getRuntime(), typeId)};
157+
}
158+
}
159+
}

0 commit comments

Comments
 (0)