package org.msgpack.jruby; import java.util.Arrays; import java.nio.ByteBuffer; import org.jruby.Ruby; import org.jruby.RubyClass; import org.jruby.RubyObject; import org.jruby.RubyFixnum; import org.jruby.RubyString; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; import org.jruby.util.ByteList; import static org.jruby.runtime.Visibility.PRIVATE; import org.jcodings.Encoding; import static org.msgpack.jruby.Types.*; @JRubyClass(name="MessagePack::ExtensionValue") public class ExtensionValue extends RubyObject { private final Encoding binaryEncoding; private RubyFixnum type; private RubyString payload; public ExtensionValue(Ruby runtime, RubyClass type) { super(runtime, type); this.binaryEncoding = runtime.getEncodingService().getAscii8bitEncoding(); } public static class ExtensionValueAllocator implements ObjectAllocator { public IRubyObject allocate(Ruby runtime, RubyClass klass) { return new ExtensionValue(runtime, klass); } } public static ExtensionValue newExtensionValue(Ruby runtime, int type, byte[] payload) { ExtensionValue v = new ExtensionValue(runtime, runtime.getModule("MessagePack").getClass("ExtensionValue")); ByteList byteList = new ByteList(payload, runtime.getEncodingService().getAscii8bitEncoding()); v.initialize(runtime.getCurrentContext(), runtime.newFixnum(type), runtime.newString(byteList)); return v; } @JRubyMethod(name = "initialize", required = 2, visibility = PRIVATE) public IRubyObject initialize(ThreadContext ctx, IRubyObject type, IRubyObject payload) { this.type = (RubyFixnum) type; this.payload = (RubyString) payload; return this; } @JRubyMethod(name = "to_msgpack") public IRubyObject toMsgpack() { ByteList payloadBytes = payload.getByteList(); int payloadSize = payloadBytes.length(); int outputSize = 0; boolean fixSize = payloadSize == 1 || payloadSize == 2 || payloadSize == 4 || payloadSize == 8 || payloadSize == 16; if (fixSize) { outputSize = 2 + payloadSize; } else if (payloadSize < 0x100) { outputSize = 3 + payloadSize; } else if (payloadSize < 0x10000) { outputSize = 4 + payloadSize; } else { outputSize = 6 + payloadSize; } byte[] bytes = new byte[outputSize]; ByteBuffer buffer = ByteBuffer.wrap(bytes); if (payloadSize == 1) { buffer.put(FIXEXT1); buffer.put((byte) type.getLongValue()); buffer.put((byte) payloadBytes.get(0)); } else if (payloadSize == 2) { buffer.put(FIXEXT2); buffer.put((byte) type.getLongValue()); buffer.put(payloadBytes.unsafeBytes(), payloadBytes.begin(), 2); } else if (payloadSize == 4) { buffer.put(FIXEXT4); buffer.put((byte) type.getLongValue()); buffer.put(payloadBytes.unsafeBytes(), payloadBytes.begin(), 4); } else if (payloadSize == 8) { buffer.put(FIXEXT8); buffer.put((byte) type.getLongValue()); buffer.put(payloadBytes.unsafeBytes(), payloadBytes.begin(), 8); } else if (payloadSize == 16) { buffer.put(FIXEXT16); buffer.put((byte) type.getLongValue()); buffer.put(payloadBytes.unsafeBytes(), payloadBytes.begin(), 16); } else if (payloadSize < 0x100) { buffer.put(VAREXT8); buffer.put((byte) payloadSize); buffer.put((byte) type.getLongValue()); buffer.put(payloadBytes.unsafeBytes(), payloadBytes.begin(), payloadSize); } else if (payloadSize < 0x10000) { buffer.put(VAREXT16); buffer.putShort((short) payloadSize); buffer.put((byte) type.getLongValue()); buffer.put(payloadBytes.unsafeBytes(), payloadBytes.begin(), payloadSize); } else { buffer.put(VAREXT32); buffer.putInt(payloadSize); buffer.put((byte) type.getLongValue()); buffer.put(payloadBytes.unsafeBytes(), payloadBytes.begin(), payloadSize); } return getRuntime().newString(new ByteList(bytes, binaryEncoding, false)); } @JRubyMethod(name = {"to_s", "inspect"}) @Override public IRubyObject to_s() { IRubyObject payloadStr = payload.callMethod(getRuntime().getCurrentContext(), "inspect"); return getRuntime().newString(String.format("#", type.getLongValue(), payloadStr)); } @JRubyMethod(name = "hash") @Override public RubyFixnum hash() { long hash = payload.hashCode() & (type.getLongValue() << 56); return RubyFixnum.newFixnum(getRuntime(), hash); } @JRubyMethod(name = "eql?") public IRubyObject eql_p(ThreadContext ctx, IRubyObject o) { if (o instanceof ExtensionValue) { ExtensionValue other = (ExtensionValue) o; return getRuntime().newBoolean(this.type.callMethod(ctx, "eql?", other.type).isTrue() && this.payload.callMethod(ctx, "eql?", other.payload).isTrue()); } return getRuntime().getFalse(); } }