diff --git a/java/src/json/ext/ByteListTranscoder.java b/java/src/json/ext/ByteListTranscoder.java index 0fedcabd..78d8037c 100644 --- a/java/src/json/ext/ByteListTranscoder.java +++ b/java/src/json/ext/ByteListTranscoder.java @@ -17,8 +17,6 @@ * using UTF-8 ByteLists as both input and output. */ abstract class ByteListTranscoder { - protected final ThreadContext context; - protected ByteList src; protected int srcEnd; /** Position where the last read character started */ @@ -26,7 +24,6 @@ abstract class ByteListTranscoder { /** Position of the next character to read */ protected int pos; - private OutputStream out; /** * When a character that can be copied straight into the output is found, * its index is stored on this variable, and copying is delayed until @@ -36,20 +33,15 @@ abstract class ByteListTranscoder { */ private int quoteStart = -1; - protected ByteListTranscoder(ThreadContext context) { - this.context = context; - } - - protected void init(ByteList src, OutputStream out) { - this.init(src, 0, src.length(), out); + protected void init(ByteList src) { + this.init(src, 0, src.length()); } - protected void init(ByteList src, int start, int end, OutputStream out) { + protected void init(ByteList src, int start, int end) { this.src = src; this.pos = start; this.charStart = start; this.srcEnd = end; - this.out = out; } /** @@ -70,52 +62,57 @@ private char next() { * Reads an UTF-8 character from the input and returns its code point, * while advancing the input position. * - *

Raises an {@link #invalidUtf8()} exception if an invalid byte + *

Raises an {@link #invalidUtf8(ThreadContext)} exception if an invalid byte * is found. */ - protected int readUtf8Char() { + protected int readUtf8Char(ThreadContext context) { charStart = pos; char head = next(); if (head <= 0x7f) { // 0b0xxxxxxx (ASCII) return head; } if (head <= 0xbf) { // 0b10xxxxxx - throw invalidUtf8(); // tail byte with no head + throw invalidUtf8(context); // tail byte with no head } if (head <= 0xdf) { // 0b110xxxxx - ensureMin(1); + ensureMin(context, 1); int cp = ((head & 0x1f) << 6) - | nextPart(); - if (cp < 0x0080) throw invalidUtf8(); + | nextPart(context); + if (cp < 0x0080) throw invalidUtf8(context); return cp; } if (head <= 0xef) { // 0b1110xxxx - ensureMin(2); + ensureMin(context, 2); int cp = ((head & 0x0f) << 12) - | (nextPart() << 6) - | nextPart(); - if (cp < 0x0800) throw invalidUtf8(); + | (nextPart(context) << 6) + | nextPart(context); + if (cp < 0x0800) throw invalidUtf8(context); return cp; } if (head <= 0xf7) { // 0b11110xxx - ensureMin(3); + ensureMin(context, 3); int cp = ((head & 0x07) << 18) - | (nextPart() << 12) - | (nextPart() << 6) - | nextPart(); - if (!Character.isValidCodePoint(cp)) throw invalidUtf8(); + | (nextPart(context) << 12) + | (nextPart(context) << 6) + | nextPart(context); + if (!Character.isValidCodePoint(cp)) throw invalidUtf8(context); return cp; } // 0b11111xxx? - throw invalidUtf8(); + throw invalidUtf8(context); + } + + protected int readASCIIChar() { + charStart = pos; + return next(); } /** * Throws a GeneratorError if the input list doesn't have at least this * many bytes left. */ - protected void ensureMin(int n) { - if (pos + n > srcEnd) throw incompleteUtf8(); + protected void ensureMin(ThreadContext context, int n) { + if (pos + n > srcEnd) throw incompleteUtf8(context); } /** @@ -124,10 +121,10 @@ protected void ensureMin(int n) { * *

Throws a GeneratorError if the byte is not a valid tail. */ - private int nextPart() { + private int nextPart(ThreadContext context) { char c = next(); // tail bytes must be 0b10xxxxxx - if ((c & 0xc0) != 0x80) throw invalidUtf8(); + if ((c & 0xc0) != 0x80) throw invalidUtf8(context); return c & 0x3f; } @@ -147,23 +144,19 @@ protected void quoteStart() { */ protected void quoteStop(int endPos) throws IOException { if (quoteStart != -1) { - out.write(src.bytes(), quoteStart, endPos - quoteStart); + append(src.unsafeBytes(), src.begin() + quoteStart, endPos - quoteStart); quoteStart = -1; } } - protected void append(int b) throws IOException { - out.write(b); - } + protected abstract void append(int b) throws IOException; - protected void append(byte[] origin, int start, int length) throws IOException { - out.write(origin, start, length); - } + protected abstract void append(byte[] origin, int start, int length) throws IOException; - protected abstract RaiseException invalidUtf8(); + protected abstract RaiseException invalidUtf8(ThreadContext context); - protected RaiseException incompleteUtf8() { - return invalidUtf8(); + protected RaiseException incompleteUtf8(ThreadContext context) { + return invalidUtf8(context); } } diff --git a/java/src/json/ext/Generator.java b/java/src/json/ext/Generator.java index 65c30ffa..9f5f7e86 100644 --- a/java/src/json/ext/Generator.java +++ b/java/src/json/ext/Generator.java @@ -6,29 +6,41 @@ package json.ext; import org.jcodings.Encoding; +import org.jcodings.specific.ASCIIEncoding; +import org.jcodings.specific.USASCIIEncoding; import org.jcodings.specific.UTF8Encoding; import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyBasicObject; import org.jruby.RubyBignum; import org.jruby.RubyBoolean; +import org.jruby.RubyClass; import org.jruby.RubyFixnum; import org.jruby.RubyFloat; import org.jruby.RubyHash; -import org.jruby.RubyIO; import org.jruby.RubyString; +import org.jruby.RubySymbol; import org.jruby.runtime.Helpers; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.util.ByteList; import org.jruby.exceptions.RaiseException; +import org.jruby.util.ConvertBytes; import org.jruby.util.IOOutputStream; +import org.jruby.util.StringSupport; +import org.jruby.util.TypeConverter; +import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; -import java.nio.charset.StandardCharsets; +import java.math.BigInteger; + +import static java.nio.charset.StandardCharsets.*; public final class Generator { + + private static final int IO_BUFFER_SIZE = 8192; + private Generator() { throw new RuntimeException(); } @@ -36,21 +48,28 @@ private Generator() { /** * Encodes the given object as a JSON string, using the given handler. */ - static RubyString - generateJson(ThreadContext context, T object, - Handler handler, IRubyObject[] args) { - Session session = new Session(context, args.length > 0 ? args[0] : null); - return session.infect(handler.generateNew(session, object)); + static RubyString generateJson(ThreadContext context, T object, Handler handler) { + Session session = new Session(null); + return handler.generateNew(context, session, object); + } + + static RubyString generateJson(ThreadContext context, T object, Handler handler, IRubyObject arg0) { + Session session = new Session(arg0); + return handler.generateNew(context, session, object); } /** * Encodes the given object as a JSON string, detecting the appropriate handler * for the given object. */ - static RubyString - generateJson(ThreadContext context, T object, IRubyObject[] args) { + static RubyString generateJson(ThreadContext context, T object) { + Handler handler = getHandlerFor(context.runtime, object); + return generateJson(context, object, handler); + } + + static RubyString generateJson(ThreadContext context, T object, IRubyObject arg0) { Handler handler = getHandlerFor(context.runtime, object); - return generateJson(context, object, handler, args); + return generateJson(context, object, handler, arg0); } /** @@ -60,14 +79,15 @@ private Generator() { public static IRubyObject generateJson(ThreadContext context, T object, GeneratorState config, IRubyObject io) { - Session session = new Session(context, config); + Session session = new Session(config); Handler handler = getHandlerFor(context.runtime, object); if (io.isNil()) { - return handler.generateNew(session, object); + return handler.generateNew(context, session, object); } - handler.generateToBuffer(session, object, new IOOutputStream(io)); + BufferedOutputStream buffer = new BufferedOutputStream(new IOOutputStream(io), IO_BUFFER_SIZE); + handler.generateToBuffer(context, session, object, buffer); return io; } @@ -79,21 +99,21 @@ private Generator() { @SuppressWarnings("unchecked") private static Handler getHandlerFor(Ruby runtime, T object) { switch (((RubyBasicObject) object).getNativeClassIndex()) { - case NIL : return (Handler) NIL_HANDLER; - case TRUE : return (Handler) TRUE_HANDLER; - case FALSE : return (Handler) FALSE_HANDLER; - case FLOAT : return (Handler) FLOAT_HANDLER; - case FIXNUM : return (Handler) FIXNUM_HANDLER; - case BIGNUM : return (Handler) BIGNUM_HANDLER; + case NIL : return NIL_HANDLER; + case TRUE : return (Handler) TRUE_HANDLER; + case FALSE : return (Handler) FALSE_HANDLER; + case FLOAT : return (Handler) FLOAT_HANDLER; + case FIXNUM : return (Handler) FIXNUM_HANDLER; + case BIGNUM : return (Handler) BIGNUM_HANDLER; case STRING : - if (((RubyBasicObject) object).getMetaClass() != runtime.getString()) break; - return (Handler) STRING_HANDLER; + if (Helpers.metaclass(object) != runtime.getString()) break; + return (Handler) STRING_HANDLER; case ARRAY : - if (((RubyBasicObject) object).getMetaClass() != runtime.getArray()) break; - return (Handler) ARRAY_HANDLER; + if (Helpers.metaclass(object) != runtime.getArray()) break; + return (Handler) ARRAY_HANDLER; case HASH : - if (((RubyBasicObject) object).getMetaClass() != runtime.getHash()) break; - return (Handler) HASH_HANDLER; + if (Helpers.metaclass(object) != runtime.getHash()) break; + return (Handler) HASH_HANDLER; } return GENERIC_HANDLER; } @@ -109,67 +129,43 @@ private static Handler getHandlerFor(Ruby run * object; any handler directly called by container handlers (arrays and * hashes/objects) shares this object with its caller. * - *

Note that anything called indirectly (via {@link GENERIC_HANDLER}) + *

Note that anything called indirectly (via {@link #GENERIC_HANDLER}) * won't be part of the session. */ static class Session { - private final ThreadContext context; private GeneratorState state; private IRubyObject possibleState; private RuntimeInfo info; private StringEncoder stringEncoder; - private boolean tainted = false; - private boolean untrusted = false; - - Session(ThreadContext context, GeneratorState state) { - this.context = context; + Session(GeneratorState state) { this.state = state; } - Session(ThreadContext context, IRubyObject possibleState) { - this.context = context; + Session(IRubyObject possibleState) { this.possibleState = possibleState == null || possibleState.isNil() ? null : possibleState; } - public ThreadContext getContext() { - return context; - } - - public Ruby getRuntime() { - return context.getRuntime(); - } - - public GeneratorState getState() { + public GeneratorState getState(ThreadContext context) { if (state == null) { - state = GeneratorState.fromState(context, getInfo(), possibleState); + state = GeneratorState.fromState(context, getInfo(context), possibleState); } return state; } - public RuntimeInfo getInfo() { - if (info == null) info = RuntimeInfo.forRuntime(getRuntime()); + public RuntimeInfo getInfo(ThreadContext context) { + if (info == null) info = RuntimeInfo.forRuntime(context.runtime); return info; } - public StringEncoder getStringEncoder() { + public StringEncoder getStringEncoder(ThreadContext context) { if (stringEncoder == null) { - stringEncoder = new StringEncoder(context, getState().asciiOnly(), getState().scriptSafe()); + GeneratorState state = getState(context); + stringEncoder = new StringEncoder(state.asciiOnly(), state.scriptSafe()); } return stringEncoder; } - - public void infectBy(IRubyObject object) { - if (object.isTaint()) tainted = true; - if (object.isUntrusted()) untrusted = true; - } - - public T infect(T object) { - if (tainted) object.setTaint(true); - if (untrusted) object.setUntrusted(true); - return object; - } } @@ -181,25 +177,26 @@ private static abstract class Handler { * given object will take. Used for allocating enough buffer space * before invoking other methods. */ - int guessSize(Session session, T object) { + int guessSize(ThreadContext context, Session session, T object) { return 4; } - RubyString generateNew(Session session, T object) { - ByteListDirectOutputStream buffer = new ByteListDirectOutputStream(guessSize(session, object)); - generateToBuffer(session, object, buffer); - return RubyString.newString(session.getRuntime(), buffer.toByteListDirect(UTF8Encoding.INSTANCE)); + RubyString generateNew(ThreadContext context, Session session, T object) { + ByteListDirectOutputStream buffer = new ByteListDirectOutputStream(guessSize(context, session, object)); + generateToBuffer(context, session, object, buffer); + return RubyString.newString(context.runtime, buffer.toByteListDirect(UTF8Encoding.INSTANCE)); } - void generateToBuffer(Session session, T object, OutputStream buffer) { + void generateToBuffer(ThreadContext context, Session session, T object, OutputStream buffer) { try { - generate(session, object, buffer); + generate(context, session, object, buffer); + buffer.flush(); } catch (IOException ioe) { - throw session.getRuntime().newIOErrorFromException(ioe); + throw context.runtime.newIOErrorFromException(ioe); } } - abstract void generate(Session session, T object, OutputStream buffer) throws IOException; + abstract void generate(ThreadContext context, Session session, T object, OutputStream buffer) throws IOException; } /** @@ -207,25 +204,25 @@ void generateToBuffer(Session session, T object, OutputStream buffer) { */ private static class KeywordHandler extends Handler { - private String keyword; + private final byte[] keyword; private KeywordHandler(String keyword) { - this.keyword = keyword; + this.keyword = keyword.getBytes(UTF_8); } @Override - int guessSize(Session session, T object) { - return keyword.length(); + int guessSize(ThreadContext context, Session session, T object) { + return keyword.length; } @Override - RubyString generateNew(Session session, T object) { - return RubyString.newString(session.getRuntime(), keyword); + RubyString generateNew(ThreadContext context, Session session, T object) { + return RubyString.newStringShared(context.runtime, keyword); } @Override - void generate(Session session, T object, OutputStream buffer) throws IOException { - buffer.write(keyword.getBytes(StandardCharsets.UTF_8)); + void generate(ThreadContext context, Session session, T object, OutputStream buffer) throws IOException { + buffer.write(keyword); } } @@ -235,48 +232,42 @@ void generate(Session session, T object, OutputStream buffer) throws IOException static final Handler BIGNUM_HANDLER = new Handler() { @Override - void generate(Session session, RubyBignum object, OutputStream buffer) throws IOException { - // JRUBY-4751: RubyBignum.to_s() returns generic object - // representation (fixed in 1.5, but we maintain backwards - // compatibility; call to_s(IRubyObject[]) then - ByteList bytes = ((RubyString) object.to_s(IRubyObject.NULL_ARRAY)).getByteList(); - buffer.write(bytes.unsafeBytes(), bytes.begin(), bytes.length()); + void generate(ThreadContext context, Session session, RubyBignum object, OutputStream buffer) throws IOException { + BigInteger bigInt = object.getValue(); + buffer.write(bigInt.toString().getBytes(UTF_8)); } }; static final Handler FIXNUM_HANDLER = new Handler() { @Override - void generate(Session session, RubyFixnum object, OutputStream buffer) throws IOException { - ByteList bytes = object.to_s().getByteList(); - buffer.write(bytes.unsafeBytes(), bytes.begin(), bytes.length()); + void generate(ThreadContext context, Session session, RubyFixnum object, OutputStream buffer) throws IOException { + buffer.write(ConvertBytes.longToCharBytes(object.getLongValue())); } }; static final Handler FLOAT_HANDLER = new Handler() { @Override - void generate(Session session, RubyFloat object, OutputStream buffer) throws IOException { - if (object.isInfinite() || object.isNaN()) { - if (!session.getState().allowNaN()) { - throw Utils.newException(session.getContext(), - Utils.M_GENERATOR_ERROR, - object + " not allowed in JSON"); + void generate(ThreadContext context, Session session, RubyFloat object, OutputStream buffer) throws IOException { + double value = object.getValue(); + + if (Double.isInfinite(value) || Double.isNaN(value)) { + if (!session.getState(context).allowNaN()) { + throw Utils.newException(context, Utils.M_GENERATOR_ERROR, object + " not allowed in JSON"); } } - double value = RubyFloat.num2dbl(object); - - buffer.write(Double.toString(value).getBytes(StandardCharsets.UTF_8)); + buffer.write(Double.toString(value).getBytes(UTF_8)); } }; private static final byte[] EMPTY_ARRAY_BYTES = "[]".getBytes(); - static final Handler ARRAY_HANDLER = - new Handler() { + static final Handler> ARRAY_HANDLER = + new Handler>() { @Override - int guessSize(Session session, RubyArray object) { - GeneratorState state = session.getState(); + int guessSize(ThreadContext context, Session session, RubyArray object) { + GeneratorState state = session.getState(context); int depth = state.getDepth(); int perItem = 4 // prealloc @@ -286,11 +277,9 @@ int guessSize(Session session, RubyArray object) { } @Override - void generate(Session session, RubyArray object, OutputStream buffer) throws IOException { - ThreadContext context = session.getContext(); - Ruby runtime = context.getRuntime(); - GeneratorState state = session.getState(); - int depth = state.increaseDepth(); + void generate(ThreadContext context, Session session, RubyArray object, OutputStream buffer) throws IOException { + GeneratorState state = session.getState(context); + int depth = state.increaseDepth(context); if (object.isEmpty()) { buffer.write(EMPTY_ARRAY_BYTES); @@ -298,6 +287,8 @@ void generate(Session session, RubyArray object, OutputStream buffer) throws IOE return; } + Ruby runtime = context.runtime; + ByteList indentUnit = state.getIndent(); byte[] shift = Utils.repeat(indentUnit, depth); @@ -307,26 +298,24 @@ void generate(Session session, RubyArray object, OutputStream buffer) throws IOE System.arraycopy(arrayNl.unsafeBytes(), arrayNl.begin(), delim, 1, arrayNl.length()); - session.infectBy(object); - buffer.write((byte)'['); buffer.write(arrayNl.bytes()); boolean firstItem = true; + for (int i = 0, t = object.getLength(); i < t; i++) { IRubyObject element = object.eltInternal(i); - session.infectBy(element); if (firstItem) { firstItem = false; } else { buffer.write(delim); } buffer.write(shift); - Handler handler = (Handler) getHandlerFor(runtime, element); - handler.generate(session, element, buffer); + Handler handler = getHandlerFor(runtime, element); + handler.generate(context, session, element, buffer); } state.decreaseDepth(); - if (arrayNl.length() != 0) { + if (!arrayNl.isEmpty()) { buffer.write(arrayNl.bytes()); buffer.write(shift, 0, state.getDepth() * indentUnit.length()); } @@ -339,8 +328,8 @@ void generate(Session session, RubyArray object, OutputStream buffer) throws IOE static final Handler HASH_HANDLER = new Handler() { @Override - int guessSize(Session session, RubyHash object) { - GeneratorState state = session.getState(); + int guessSize(ThreadContext context, Session session, RubyHash object) { + GeneratorState state = session.getState(context); int perItem = 12 // key, colon, comma + (state.getDepth() + 1) * state.getIndent().length() @@ -350,12 +339,9 @@ int guessSize(Session session, RubyHash object) { } @Override - void generate(final Session session, RubyHash object, - final OutputStream buffer) throws IOException { - ThreadContext context = session.getContext(); - final Ruby runtime = context.getRuntime(); - final GeneratorState state = session.getState(); - final int depth = state.increaseDepth(); + void generate(ThreadContext context, final Session session, RubyHash object, final OutputStream buffer) throws IOException { + final GeneratorState state = session.getState(context); + final int depth = state.increaseDepth(context); if (object.isEmpty()) { buffer.write(EMPTY_HASH_BYTES); @@ -364,51 +350,65 @@ void generate(final Session session, RubyHash object, } final ByteList objectNl = state.getObjectNl(); + byte[] objectNLBytes = objectNl.unsafeBytes(); final byte[] indent = Utils.repeat(state.getIndent(), depth); final ByteList spaceBefore = state.getSpaceBefore(); final ByteList space = state.getSpace(); buffer.write((byte)'{'); - buffer.write(objectNl.bytes()); + buffer.write(objectNLBytes); final boolean[] firstPair = new boolean[]{true}; - object.visitAll(new RubyHash.Visitor() { + object.visitAll(context, new RubyHash.VisitorWithState() { @Override - public void visit(IRubyObject key, IRubyObject value) { + public void visit(ThreadContext context, RubyHash self, IRubyObject key, IRubyObject value, int index, boolean[] firstPair) { try { if (firstPair[0]) { firstPair[0] = false; } else { buffer.write((byte) ','); - buffer.write(objectNl.bytes()); + buffer.write(objectNLBytes); + } + if (!objectNl.isEmpty()) buffer.write(indent); + + Ruby runtime = context.runtime; + + IRubyObject keyStr; + RubyClass keyClass = key.getType(); + if (key instanceof RubyString) { + if (keyClass == runtime.getString()) { + keyStr = key; + } else { + keyStr = key.callMethod(context, "to_s"); + } + } else if (keyClass == runtime.getSymbol()) { + keyStr = key.asString(); + } else { + keyStr = TypeConverter.convertToType(key, runtime.getString(), "to_s"); } - if (objectNl.length() != 0) buffer.write(indent); - IRubyObject keyStr = key.callMethod(context, "to_s"); if (keyStr.getMetaClass() == runtime.getString()) { - STRING_HANDLER.generate(session, (RubyString) keyStr, buffer); + STRING_HANDLER.generate(context, session, (RubyString) keyStr, buffer); } else { Utils.ensureString(keyStr); - Handler keyHandler = (Handler) getHandlerFor(runtime, keyStr); - keyHandler.generate(session, keyStr, buffer); + Handler keyHandler = getHandlerFor(runtime, keyStr); + keyHandler.generate(context, session, keyStr, buffer); } - session.infectBy(key); - buffer.write(spaceBefore.bytes()); + buffer.write(spaceBefore.unsafeBytes()); buffer.write((byte) ':'); - buffer.write(space.bytes()); + buffer.write(space.unsafeBytes()); - Handler valueHandler = (Handler) getHandlerFor(runtime, value); - valueHandler.generate(session, value, buffer); - session.infectBy(value); + Handler valueHandler = getHandlerFor(runtime, value); + valueHandler.generate(context, session, value, buffer); } catch (Throwable t) { Helpers.throwException(t); } } - }); + }, firstPair); state.decreaseDepth(); - if (!firstPair[0] && objectNl.length() != 0) { - buffer.write(objectNl.bytes()); + if (!firstPair[0] && !objectNl.isEmpty()) { + buffer.write(objectNLBytes); } buffer.write(Utils.repeat(state.getIndent(), state.getDepth())); buffer.write((byte)'}'); @@ -418,7 +418,7 @@ public void visit(IRubyObject key, IRubyObject value) { static final Handler STRING_HANDLER = new Handler() { @Override - int guessSize(Session session, RubyString object) { + int guessSize(ThreadContext context, Session session, RubyString object) { // for most applications, most strings will be just a set of // printable ASCII characters without any escaping, so let's // just allocate enough space for that + the quotes @@ -426,32 +426,56 @@ int guessSize(Session session, RubyString object) { } @Override - void generate(Session session, RubyString object, OutputStream buffer) throws IOException { - RuntimeInfo info = session.getInfo(); - RubyString src; - + void generate(ThreadContext context, Session session, RubyString object, OutputStream buffer) throws IOException { try { - if (object.encoding(session.getContext()) != info.utf8.get()) { - src = (RubyString)object.encode(session.getContext(), - info.utf8.get()); - } else { - src = object; + object = ensureValidEncoding(context, object); + StringEncoder stringEncoder = session.getStringEncoder(context); + ByteList byteList = object.getByteList(); + switch (object.scanForCodeRange()) { + case StringSupport.CR_7BIT: + stringEncoder.encodeASCII(context, byteList, buffer); + break; + case StringSupport.CR_VALID: + stringEncoder.encode(context, byteList, buffer); + break; + default: + throw stringEncoder.invalidUtf8(context); } - - session.getStringEncoder().encode(src.getByteList(), buffer); } catch (RaiseException re) { - throw Utils.newException(session.getContext(), Utils.M_GENERATOR_ERROR, - re.getMessage()); + throw Utils.newException(context, Utils.M_GENERATOR_ERROR, re.getMessage()); } } }; + static RubyString ensureValidEncoding(ThreadContext context, RubyString str) { + Encoding encoding = str.getEncoding(); + RubyString utf8String; + if (!(encoding == USASCIIEncoding.INSTANCE || encoding == UTF8Encoding.INSTANCE)) { + if (encoding == ASCIIEncoding.INSTANCE) { + utf8String = str.strDup(context.runtime); + utf8String.setEncoding(UTF8Encoding.INSTANCE); + switch (utf8String.getCodeRange()) { + case StringSupport.CR_7BIT: + return utf8String; + case StringSupport.CR_VALID: + // For historical reason, we silently reinterpret binary strings as UTF-8 if it would work. + // TODO: Raise in 3.0.0 + context.runtime.getWarnings().warn("JSON.generate: UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0"); + return utf8String; + } + } + + str = (RubyString) str.encode(context, context.runtime.getEncodingService().convertEncodingToRubyEncoding(UTF8Encoding.INSTANCE)); + } + return str; + } + static final Handler TRUE_HANDLER = - new KeywordHandler("true"); + new KeywordHandler<>("true"); static final Handler FALSE_HANDLER = - new KeywordHandler("false"); + new KeywordHandler<>("false"); static final Handler NIL_HANDLER = - new KeywordHandler("null"); + new KeywordHandler<>("null"); /** * The default handler (Object#to_json): coerces the object @@ -460,15 +484,15 @@ void generate(Session session, RubyString object, OutputStream buffer) throws IO static final Handler OBJECT_HANDLER = new Handler() { @Override - RubyString generateNew(Session session, IRubyObject object) { + RubyString generateNew(ThreadContext context, Session session, IRubyObject object) { RubyString str = object.asString(); - return STRING_HANDLER.generateNew(session, str); + return STRING_HANDLER.generateNew(context, session, str); } @Override - void generate(Session session, IRubyObject object, OutputStream buffer) throws IOException { + void generate(ThreadContext context, Session session, IRubyObject object, OutputStream buffer) throws IOException { RubyString str = object.asString(); - STRING_HANDLER.generate(session, str, buffer); + STRING_HANDLER.generate(context, session, str, buffer); } }; @@ -479,24 +503,22 @@ void generate(Session session, IRubyObject object, OutputStream buffer) throws I static final Handler GENERIC_HANDLER = new Handler() { @Override - RubyString generateNew(Session session, IRubyObject object) { - if (session.getState().strict()) { - throw Utils.newException(session.getContext(), - Utils.M_GENERATOR_ERROR, - object + " not allowed in JSON"); + RubyString generateNew(ThreadContext context, Session session, IRubyObject object) { + GeneratorState state = session.getState(context); + if (state.strict()) { + throw Utils.newException(context, Utils.M_GENERATOR_ERROR, object + " not allowed in JSON"); } else if (object.respondsTo("to_json")) { - IRubyObject result = object.callMethod(session.getContext(), "to_json", - new IRubyObject[] {session.getState()}); + IRubyObject result = object.callMethod(context, "to_json", state); if (result instanceof RubyString) return (RubyString)result; - throw session.getRuntime().newTypeError("to_json must return a String"); + throw context.runtime.newTypeError("to_json must return a String"); } else { - return OBJECT_HANDLER.generateNew(session, object); + return OBJECT_HANDLER.generateNew(context, session, object); } } @Override - void generate(Session session, IRubyObject object, OutputStream buffer) throws IOException { - RubyString result = generateNew(session, object); + void generate(ThreadContext context, Session session, IRubyObject object, OutputStream buffer) throws IOException { + RubyString result = generateNew(context, session, object); ByteList bytes = result.getByteList(); buffer.write(bytes.unsafeBytes(), bytes.begin(), bytes.length()); } diff --git a/java/src/json/ext/GeneratorMethods.java b/java/src/json/ext/GeneratorMethods.java index bde7a18d..eb31b9ef 100644 --- a/java/src/json/ext/GeneratorMethods.java +++ b/java/src/json/ext/GeneratorMethods.java @@ -12,7 +12,6 @@ import org.jruby.RubyFixnum; import org.jruby.RubyFloat; import org.jruby.RubyHash; -import org.jruby.RubyInteger; import org.jruby.RubyModule; import org.jruby.RubyNumeric; import org.jruby.RubyString; @@ -30,8 +29,8 @@ class GeneratorMethods { /** * Populates the given module with all modules and their methods - * @param info - * @param generatorMethodsModule The module to populate + * @param info The current RuntimeInfo + * @param module The module to populate * (normally JSON::Generator::GeneratorMethods) */ static void populate(RuntimeInfo info, RubyModule module) { @@ -45,65 +44,79 @@ static void populate(RuntimeInfo info, RubyModule module) { defineMethods(module, "String", RbString.class); defineMethods(module, "TrueClass", RbTrue.class); - info.stringExtendModule = new WeakReference(module.defineModuleUnder("String") - .defineModuleUnder("Extend")); - info.stringExtendModule.get().defineAnnotatedMethods(StringExtend.class); + RubyModule stringExtend = module.defineModuleUnder("String").defineModuleUnder("Extend"); + stringExtend.defineAnnotatedMethods(StringExtend.class); } /** * Convenience method for defining methods on a submodule. - * @param parentModule - * @param submoduleName - * @param klass + * @param parentModule the parent module + * @param submoduleName the submodule + * @param klass the class from which to define methods */ private static void defineMethods(RubyModule parentModule, - String submoduleName, Class klass) { + String submoduleName, Class klass) { RubyModule submodule = parentModule.defineModuleUnder(submoduleName); submodule.defineAnnotatedMethods(klass); } - public static class RbHash { - @JRubyMethod(rest=true) - public static IRubyObject to_json(ThreadContext context, - IRubyObject vSelf, IRubyObject[] args) { - return Generator.generateJson(context, (RubyHash)vSelf, - Generator.HASH_HANDLER, args); + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf) { + return Generator.generateJson(context, (RubyHash)vSelf, Generator.HASH_HANDLER); + } + + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf, IRubyObject arg0) { + return Generator.generateJson(context, (RubyHash)vSelf, Generator.HASH_HANDLER, arg0); } } public static class RbArray { - @JRubyMethod(rest=true) - public static IRubyObject to_json(ThreadContext context, - IRubyObject vSelf, IRubyObject[] args) { - return Generator.generateJson(context, (RubyArray)vSelf, - Generator.ARRAY_HANDLER, args); + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf) { + return Generator.generateJson(context, (RubyArray)vSelf, Generator.ARRAY_HANDLER); + } + + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf, IRubyObject arg0) { + return Generator.generateJson(context, (RubyArray)vSelf, Generator.ARRAY_HANDLER, arg0); } } public static class RbInteger { - @JRubyMethod(rest=true) - public static IRubyObject to_json(ThreadContext context, - IRubyObject vSelf, IRubyObject[] args) { - return Generator.generateJson(context, vSelf, args); + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf) { + return Generator.generateJson(context, vSelf); + } + + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf, IRubyObject arg0) { + return Generator.generateJson(context, vSelf, arg0); } } public static class RbFloat { - @JRubyMethod(rest=true) - public static IRubyObject to_json(ThreadContext context, - IRubyObject vSelf, IRubyObject[] args) { - return Generator.generateJson(context, (RubyFloat)vSelf, - Generator.FLOAT_HANDLER, args); + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf) { + return Generator.generateJson(context, (RubyFloat)vSelf, Generator.FLOAT_HANDLER); + } + + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf, IRubyObject arg0) { + return Generator.generateJson(context, (RubyFloat)vSelf, Generator.FLOAT_HANDLER, arg0); } } public static class RbString { - @JRubyMethod(rest=true) - public static IRubyObject to_json(ThreadContext context, - IRubyObject vSelf, IRubyObject[] args) { - return Generator.generateJson(context, (RubyString)vSelf, - Generator.STRING_HANDLER, args); + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf) { + return Generator.generateJson(context, (RubyString)vSelf, Generator.STRING_HANDLER); + } + + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf, IRubyObject arg0) { + return Generator.generateJson(context, (RubyString)vSelf, Generator.STRING_HANDLER, arg0); } /** @@ -112,12 +125,16 @@ public static IRubyObject to_json(ThreadContext context, *

This method creates a JSON text from the result of a call to * {@link #to_json_raw_object} of this String. */ - @JRubyMethod(rest=true) - public static IRubyObject to_json_raw(ThreadContext context, - IRubyObject vSelf, IRubyObject[] args) { + @JRubyMethod + public static IRubyObject to_json_raw(ThreadContext context, IRubyObject vSelf) { RubyHash obj = toJsonRawObject(context, Utils.ensureString(vSelf)); - return Generator.generateJson(context, obj, - Generator.HASH_HANDLER, args); + return Generator.generateJson(context, obj, Generator.HASH_HANDLER); + } + + @JRubyMethod + public static IRubyObject to_json_raw(ThreadContext context, IRubyObject vSelf, IRubyObject arg0) { + RubyHash obj = toJsonRawObject(context, Utils.ensureString(vSelf)); + return Generator.generateJson(context, obj, Generator.HASH_HANDLER, arg0); } /** @@ -128,15 +145,14 @@ public static IRubyObject to_json_raw(ThreadContext context, * method should be used if you want to convert raw strings to JSON * instead of UTF-8 strings, e.g. binary data. */ - @JRubyMethod(rest=true) - public static IRubyObject to_json_raw_object(ThreadContext context, - IRubyObject vSelf, IRubyObject[] args) { + @JRubyMethod + public static IRubyObject to_json_raw_object(ThreadContext context, IRubyObject vSelf) { return toJsonRawObject(context, Utils.ensureString(vSelf)); } private static RubyHash toJsonRawObject(ThreadContext context, RubyString self) { - Ruby runtime = context.getRuntime(); + Ruby runtime = context.runtime; RubyHash result = RubyHash.newHash(runtime); IRubyObject createId = RuntimeInfo.forRuntime(runtime) @@ -154,11 +170,10 @@ private static RubyHash toJsonRawObject(ThreadContext context, return result; } - @JRubyMethod(required=1, module=true) - public static IRubyObject included(ThreadContext context, - IRubyObject vSelf, IRubyObject module) { - RuntimeInfo info = RuntimeInfo.forRuntime(context.getRuntime()); - return module.callMethod(context, "extend", info.stringExtendModule.get()); + @JRubyMethod(module=true) + public static IRubyObject included(ThreadContext context, IRubyObject extendModule, IRubyObject module) { + RuntimeInfo info = RuntimeInfo.forRuntime(context.runtime); + return module.callMethod(context, "extend", ((RubyModule) extendModule).getConstant("Extend")); } } @@ -170,10 +185,10 @@ public static class StringExtend { * array for the key "raw"). The Ruby String can be created by this * module method. */ - @JRubyMethod(required=1) + @JRubyMethod public static IRubyObject json_create(ThreadContext context, IRubyObject vSelf, IRubyObject vHash) { - Ruby runtime = context.getRuntime(); + Ruby runtime = context.runtime; RubyHash o = vHash.convertToHash(); IRubyObject rawData = o.fastARef(runtime.newString("raw")); if (rawData == null) { @@ -195,37 +210,50 @@ public static IRubyObject json_create(ThreadContext context, } public static class RbTrue { - @JRubyMethod(rest=true) - public static IRubyObject to_json(ThreadContext context, - IRubyObject vSelf, IRubyObject[] args) { - return Generator.generateJson(context, (RubyBoolean)vSelf, - Generator.TRUE_HANDLER, args); + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf) { + return Generator.generateJson(context, (RubyBoolean)vSelf, Generator.TRUE_HANDLER); + } + + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf, IRubyObject arg0) { + return Generator.generateJson(context, (RubyBoolean)vSelf, Generator.TRUE_HANDLER, arg0); } } public static class RbFalse { - @JRubyMethod(rest=true) - public static IRubyObject to_json(ThreadContext context, - IRubyObject vSelf, IRubyObject[] args) { - return Generator.generateJson(context, (RubyBoolean)vSelf, - Generator.FALSE_HANDLER, args); + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf) { + return Generator.generateJson(context, (RubyBoolean)vSelf, Generator.FALSE_HANDLER); + } + + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf, IRubyObject arg0) { + return Generator.generateJson(context, (RubyBoolean)vSelf, Generator.FALSE_HANDLER, arg0); } } public static class RbNil { - @JRubyMethod(rest=true) - public static IRubyObject to_json(ThreadContext context, - IRubyObject vSelf, IRubyObject[] args) { - return Generator.generateJson(context, vSelf, - Generator.NIL_HANDLER, args); + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf) { + return Generator.generateJson(context, vSelf, Generator.NIL_HANDLER); + } + + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf, IRubyObject arg0) { + return Generator.generateJson(context, vSelf, Generator.NIL_HANDLER, arg0); } } public static class RbObject { - @JRubyMethod(rest=true) - public static IRubyObject to_json(ThreadContext context, - IRubyObject self, IRubyObject[] args) { - return RbString.to_json(context, self.asString(), args); + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject self) { + return RbString.to_json(context, self.asString()); + } + + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject self, IRubyObject arg0) { + return RbString.to_json(context, self.asString(), arg0); } } } diff --git a/java/src/json/ext/GeneratorService.java b/java/src/json/ext/GeneratorService.java index 1500c412..7d3f86e5 100644 --- a/java/src/json/ext/GeneratorService.java +++ b/java/src/json/ext/GeneratorService.java @@ -23,7 +23,8 @@ public boolean basicLoad(Ruby runtime) throws IOException { runtime.getLoadService().require("json/common"); RuntimeInfo info = RuntimeInfo.initRuntime(runtime); - info.jsonModule = new WeakReference(runtime.defineModule("JSON")); + RubyModule jsonModule = runtime.defineModule("JSON"); + info.jsonModule = new WeakReference(jsonModule); RubyModule jsonExtModule = info.jsonModule.get().defineModuleUnder("Ext"); RubyModule generatorModule = jsonExtModule.defineModuleUnder("Generator"); diff --git a/java/src/json/ext/GeneratorState.java b/java/src/json/ext/GeneratorState.java index 0d8a3617..fdd433c6 100644 --- a/java/src/json/ext/GeneratorState.java +++ b/java/src/json/ext/GeneratorState.java @@ -108,11 +108,7 @@ public class GeneratorState extends RubyObject { */ private int depth = 0; - static final ObjectAllocator ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klazz) { - return new GeneratorState(runtime, klazz); - } - }; + static final ObjectAllocator ALLOCATOR = GeneratorState::new; public GeneratorState(Ruby runtime, RubyClass metaClass) { super(runtime, metaClass); @@ -126,15 +122,13 @@ public GeneratorState(Ruby runtime, RubyClass metaClass) { * configured by opts, something else to create an * unconfigured instance. If opts is a State * object, it is just returned. - * @param clazzParam The receiver of the method call - * ({@link RubyClass} State) + * @param context The current thread context + * @param klass The receiver of the method call ({@link RubyClass} State) * @param opts The object to use as a base for the new State - * @param block The block passed to the method * @return A GeneratorState as determined above */ @JRubyMethod(meta=true) - public static IRubyObject from_state(ThreadContext context, - IRubyObject klass, IRubyObject opts) { + public static IRubyObject from_state(ThreadContext context, IRubyObject klass, IRubyObject opts) { return fromState(context, opts); } @@ -144,7 +138,7 @@ public static IRubyObject generate(ThreadContext context, IRubyObject klass, IRu } static GeneratorState fromState(ThreadContext context, IRubyObject opts) { - return fromState(context, RuntimeInfo.forRuntime(context.getRuntime()), opts); + return fromState(context, RuntimeInfo.forRuntime(context.runtime), opts); } static GeneratorState fromState(ThreadContext context, RuntimeInfo info, @@ -155,9 +149,8 @@ static GeneratorState fromState(ThreadContext context, RuntimeInfo info, if (klass.isInstance(opts)) return (GeneratorState)opts; // if the given parameter is a Hash, pass it to the instantiator - if (context.getRuntime().getHash().isInstance(opts)) { - return (GeneratorState)klass.newInstance(context, - new IRubyObject[] {opts}, Block.NULL_BLOCK); + if (context.runtime.getHash().isInstance(opts)) { + return (GeneratorState)klass.newInstance(context, opts, Block.NULL_BLOCK); } } @@ -167,9 +160,9 @@ static GeneratorState fromState(ThreadContext context, RuntimeInfo info, /** * State#initialize(opts = {}) - * + *

* Instantiates a new State object, configured by opts. - * + *

* opts can have the following keys: * *

@@ -194,15 +187,21 @@ static GeneratorState fromState(ThreadContext context, RuntimeInfo info, *
set to true if U+2028, U+2029 and forward slashes should be escaped * in the json output to make it safe to include in a JavaScript tag (default: false) */ - @JRubyMethod(optional=1, visibility=Visibility.PRIVATE) - public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { - _configure(context, args.length > 0 ? args[0] : null); + @JRubyMethod(visibility=Visibility.PRIVATE) + public IRubyObject initialize(ThreadContext context) { + _configure(context, null); + return this; + } + + @JRubyMethod(visibility=Visibility.PRIVATE) + public IRubyObject initialize(ThreadContext context, IRubyObject arg0) { + _configure(context, arg0); return this; } @JRubyMethod public IRubyObject initialize_copy(ThreadContext context, IRubyObject vOrig) { - Ruby runtime = context.getRuntime(); + Ruby runtime = context.runtime; if (!(vOrig instanceof GeneratorState)) { throw runtime.newTypeError(vOrig, getType()); } @@ -231,7 +230,7 @@ public IRubyObject initialize_copy(ThreadContext context, IRubyObject vOrig) { @JRubyMethod(visibility = Visibility.PRIVATE) public IRubyObject _generate(ThreadContext context, IRubyObject obj, IRubyObject io) { IRubyObject result = Generator.generateJson(context, obj, this, io); - RuntimeInfo info = RuntimeInfo.forRuntime(context.getRuntime()); + RuntimeInfo info = RuntimeInfo.forRuntime(context.runtime); if (!(result instanceof RubyString)) { return result; } @@ -239,25 +238,16 @@ public IRubyObject _generate(ThreadContext context, IRubyObject obj, IRubyObject RubyString resultString = result.convertToString(); if (resultString.getEncoding() != UTF8Encoding.INSTANCE) { if (resultString.isFrozen()) { - resultString = resultString.strDup(context.getRuntime()); + resultString = resultString.strDup(context.runtime); } - resultString.force_encoding(context, info.utf8.get()); + resultString.setEncoding(UTF8Encoding.INSTANCE); + resultString.clearCodeRange(); } return resultString; } - private static boolean matchClosingBrace(ByteList bl, int pos, int len, - int brace) { - for (int endPos = len - 1; endPos > pos; endPos--) { - int b = bl.get(endPos); - if (Character.isWhitespace(b)) continue; - return b == brace; - } - return false; - } - - @JRubyMethod(name="[]", required=1) + @JRubyMethod(name="[]") public IRubyObject op_aref(ThreadContext context, IRubyObject vName) { String name = vName.asJavaString(); if (getMetaClass().isMethodBound(name, true)) { @@ -268,16 +258,16 @@ public IRubyObject op_aref(ThreadContext context, IRubyObject vName) { } } - @JRubyMethod(name="[]=", required=2) + @JRubyMethod(name="[]=") public IRubyObject op_aset(ThreadContext context, IRubyObject vName, IRubyObject value) { String name = vName.asJavaString(); String nameWriter = name + "="; if (getMetaClass().isMethodBound(nameWriter, true)) { - return send(context, context.getRuntime().newString(nameWriter), value, Block.NULL_BLOCK); + return send(context, context.runtime.newString(nameWriter), value, Block.NULL_BLOCK); } else { getInstanceVariables().setInstanceVariable("@" + name, value); } - return context.getRuntime().getNil(); + return context.nil; } public ByteList getIndent() { @@ -286,7 +276,7 @@ public ByteList getIndent() { @JRubyMethod(name="indent") public RubyString indent_get(ThreadContext context) { - return context.getRuntime().newString(indent); + return context.runtime.newString(indent); } @JRubyMethod(name="indent=") @@ -301,7 +291,7 @@ public ByteList getSpace() { @JRubyMethod(name="space") public RubyString space_get(ThreadContext context) { - return context.getRuntime().newString(space); + return context.runtime.newString(space); } @JRubyMethod(name="space=") @@ -316,7 +306,7 @@ public ByteList getSpaceBefore() { @JRubyMethod(name="space_before") public RubyString space_before_get(ThreadContext context) { - return context.getRuntime().newString(spaceBefore); + return context.runtime.newString(spaceBefore); } @JRubyMethod(name="space_before=") @@ -332,7 +322,7 @@ public ByteList getObjectNl() { @JRubyMethod(name="object_nl") public RubyString object_nl_get(ThreadContext context) { - return context.getRuntime().newString(objectNl); + return context.runtime.newString(objectNl); } @JRubyMethod(name="object_nl=") @@ -348,7 +338,7 @@ public ByteList getArrayNl() { @JRubyMethod(name="array_nl") public RubyString array_nl_get(ThreadContext context) { - return context.getRuntime().newString(arrayNl); + return context.runtime.newString(arrayNl); } @JRubyMethod(name="array_nl=") @@ -360,19 +350,12 @@ public IRubyObject array_nl_set(ThreadContext context, @JRubyMethod(name="check_circular?") public RubyBoolean check_circular_p(ThreadContext context) { - return context.getRuntime().newBoolean(maxNesting != 0); - } - - /** - * Returns the maximum level of nesting configured for this state. - */ - public int getMaxNesting() { - return maxNesting; + return RubyBoolean.newBoolean(context, maxNesting != 0); } @JRubyMethod(name="max_nesting") public RubyInteger max_nesting_get(ThreadContext context) { - return context.getRuntime().newFixnum(maxNesting); + return context.runtime.newFixnum(maxNesting); } @JRubyMethod(name="max_nesting=") @@ -390,7 +373,7 @@ public boolean scriptSafe() { @JRubyMethod(name="script_safe", alias="escape_slash") public RubyBoolean script_safe_get(ThreadContext context) { - return context.getRuntime().newBoolean(scriptSafe); + return RubyBoolean.newBoolean(context, scriptSafe); } @JRubyMethod(name="script_safe=", alias="escape_slash=") @@ -401,7 +384,7 @@ public IRubyObject script_safe_set(IRubyObject script_safe) { @JRubyMethod(name="script_safe?", alias="escape_slash?") public RubyBoolean script_safe_p(ThreadContext context) { - return context.getRuntime().newBoolean(scriptSafe); + return RubyBoolean.newBoolean(context, scriptSafe); } /** @@ -413,7 +396,7 @@ public boolean strict() { @JRubyMethod(name={"strict","strict?"}) public RubyBoolean strict_get(ThreadContext context) { - return context.getRuntime().newBoolean(strict); + return RubyBoolean.newBoolean(context, strict); } @JRubyMethod(name="strict=") @@ -428,7 +411,7 @@ public boolean allowNaN() { @JRubyMethod(name="allow_nan?") public RubyBoolean allow_nan_p(ThreadContext context) { - return context.getRuntime().newBoolean(allowNaN); + return RubyBoolean.newBoolean(context, allowNaN); } public boolean asciiOnly() { @@ -437,12 +420,12 @@ public boolean asciiOnly() { @JRubyMethod(name="ascii_only?") public RubyBoolean ascii_only_p(ThreadContext context) { - return context.getRuntime().newBoolean(asciiOnly); + return RubyBoolean.newBoolean(context, asciiOnly); } @JRubyMethod(name="buffer_initial_length") public RubyInteger buffer_initial_length_get(ThreadContext context) { - return context.getRuntime().newFixnum(bufferInitialLength); + return context.runtime.newFixnum(bufferInitialLength); } @JRubyMethod(name="buffer_initial_length=") @@ -458,7 +441,7 @@ public int getDepth() { @JRubyMethod(name="depth") public RubyInteger depth_get(ThreadContext context) { - return context.getRuntime().newFixnum(depth); + return context.runtime.newFixnum(depth); } @JRubyMethod(name="depth=") @@ -469,9 +452,8 @@ public IRubyObject depth_set(IRubyObject vDepth) { private ByteList prepareByteList(ThreadContext context, IRubyObject value) { RubyString str = value.convertToString(); - RuntimeInfo info = RuntimeInfo.forRuntime(context.getRuntime()); - if (str.encoding(context) != info.utf8.get()) { - str = (RubyString)str.encode(context, info.utf8.get()); + if (str.getEncoding() != UTF8Encoding.INSTANCE) { + str = (RubyString)str.encode(context, context.runtime.getEncodingService().convertEncodingToRubyEncoding(UTF8Encoding.INSTANCE)); } return str.getByteList().dup(); } @@ -527,7 +509,7 @@ public IRubyObject _configure(ThreadContext context, IRubyObject vOpts) { */ @JRubyMethod(alias = "to_hash") public RubyHash to_h(ThreadContext context) { - Ruby runtime = context.getRuntime(); + Ruby runtime = context.runtime; RubyHash result = RubyHash.newHash(runtime); result.op_aset(context, runtime.newSymbol("indent"), indent_get(context)); @@ -548,26 +530,24 @@ public RubyHash to_h(ThreadContext context) { return result; } - public int increaseDepth() { + public int increaseDepth(ThreadContext context) { depth++; - checkMaxNesting(); + checkMaxNesting(context); return depth; } - public int decreaseDepth() { - return --depth; + public void decreaseDepth() { + --depth; } /** * Checks if the current depth is allowed as per this state's options. - * @param context - * @param depth The current depth + * @param context The current context */ - private void checkMaxNesting() { + private void checkMaxNesting(ThreadContext context) { if (maxNesting != 0 && depth > maxNesting) { depth--; - throw Utils.newException(getRuntime().getCurrentContext(), - Utils.M_NESTING_ERROR, "nesting of " + depth + " is too deep"); + throw Utils.newException(context, Utils.M_NESTING_ERROR, "nesting of " + depth + " is too deep"); } } } diff --git a/java/src/json/ext/OptionsReader.java b/java/src/json/ext/OptionsReader.java index 70426d42..ff976c38 100644 --- a/java/src/json/ext/OptionsReader.java +++ b/java/src/json/ext/OptionsReader.java @@ -5,6 +5,7 @@ */ package json.ext; +import org.jcodings.specific.UTF8Encoding; import org.jruby.Ruby; import org.jruby.RubyClass; import org.jruby.RubyHash; @@ -22,7 +23,7 @@ final class OptionsReader { OptionsReader(ThreadContext context, IRubyObject vOpts) { this.context = context; - this.runtime = context.getRuntime(); + this.runtime = context.runtime; if (vOpts == null || vOpts.isNil()) { opts = null; } else if (vOpts.respondsTo("to_hash")) { @@ -41,7 +42,7 @@ private RuntimeInfo getRuntimeInfo() { } /** - * Efficiently looks up items with a {@link RubySymbol Symbol} key + * Efficiently looks up items with a {@link org.jruby.RubySymbol Symbol} key * @param key The Symbol name to look up for * @return The item in the {@link RubyHash Hash}, or null * if not found @@ -69,7 +70,7 @@ int getInt(String key, int defaultValue) { * @param key The Symbol name to look up for * @return null if the key is not in the Hash or if * its value evaluates to false - * @throws RaiseException TypeError if the value does not + * @throws org.jruby.exceptions.RaiseException TypeError if the value does not * evaluate to false and can't be * converted to string */ @@ -83,9 +84,8 @@ RubyString getString(String key, RubyString defaultValue) { if (value == null || !value.isTrue()) return defaultValue; RubyString str = value.convertToString(); - RuntimeInfo info = getRuntimeInfo(); - if (str.encoding(context) != info.utf8.get()) { - str = (RubyString)str.encode(context, info.utf8.get()); + if (str.getEncoding() != UTF8Encoding.INSTANCE) { + str = (RubyString)str.encode(context, context.runtime.getEncodingService().convertEncodingToRubyEncoding(UTF8Encoding.INSTANCE)); } return str; } diff --git a/java/src/json/ext/Parser.java b/java/src/json/ext/Parser.java index 74037d37..47e66795 100644 --- a/java/src/json/ext/Parser.java +++ b/java/src/json/ext/Parser.java @@ -7,10 +7,12 @@ */ package json.ext; +import org.jcodings.Encoding; +import org.jcodings.specific.ASCIIEncoding; +import org.jcodings.specific.UTF8Encoding; import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyClass; -import org.jruby.RubyEncoding; import org.jruby.RubyFloat; import org.jruby.RubyHash; import org.jruby.RubyInteger; @@ -20,13 +22,13 @@ import org.jruby.exceptions.JumpException; import org.jruby.exceptions.RaiseException; import org.jruby.runtime.Block; +import org.jruby.runtime.Helpers; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.util.ByteList; import org.jruby.util.ConvertBytes; -import org.jruby.util.ConvertDouble; import java.util.function.BiFunction; @@ -41,7 +43,7 @@ * This is performed for you when you include "json/ext". * *

This class does not perform the actual parsing, just acts as an interface - * to Ruby code. When the {@link #parse()} method is invoked, a + * to Ruby code. When the {@link #parse(ThreadContext)} method is invoked, a * Parser.ParserSession object is instantiated, which handles the process. * * @author mernen @@ -71,11 +73,7 @@ public class Parser extends RubyObject { private static final String CONST_INFINITY = "Infinity"; private static final String CONST_MINUS_INFINITY = "MinusInfinity"; - static final ObjectAllocator ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klazz) { - return new Parser(runtime, klazz); - } - }; + static final ObjectAllocator ALLOCATOR = Parser::new; /** * Multiple-value return for internal parser methods. @@ -113,42 +111,42 @@ public Parser(Ruby runtime, RubyClass metaClass) { * source. * It will be configured by the opts Hash. * opts can have the following keys: - * + *

*

*
:max_nesting *
The maximum depth of nesting allowed in the parsed data * structures. Disable depth checking with :max_nesting => false|nil|0, * it defaults to 100. - * + *

*

:allow_nan *
If set to true, allow NaN, * Infinity and -Infinity in defiance of RFC 4627 * to be parsed by the Parser. This option defaults to false. - * + *

*

:allow_trailing_comma *
If set to true, allow arrays and objects with a trailing * comma in defiance of RFC 4627 to be parsed by the Parser. * This option defaults to false. - * + *

*

:symbolize_names *
If set to true, returns symbols for the names (keys) in * a JSON object. Otherwise strings are returned, which is also the default. - * + *

*

:create_additions *
If set to false, the Parser doesn't create additions * even if a matching class and create_id was found. This option * defaults to true. - * + *

*

:object_class *
Defaults to Hash. If another type is provided, it will be used * instead of Hash to represent JSON objects. The type must respond to * new without arguments, and return an object that respond to []=. - * + *

*

:array_class *
Defaults to Array. If another type is provided, it will be used * instead of Hash to represent JSON arrays. The type must respond to * new without arguments, and return an object that respond to <<. - * + *

*

:decimal_class *
Specifies which class to use instead of the default (Float) when * parsing decimal numbers. This class must accept a single string argument @@ -156,31 +154,44 @@ public Parser(Ruby runtime, RubyClass metaClass) { *
*/ - @JRubyMethod(name = "new", required = 1, optional = 1, meta = true) - public static IRubyObject newInstance(IRubyObject clazz, IRubyObject[] args, Block block) { + @JRubyMethod(name = "new", meta = true) + public static IRubyObject newInstance(IRubyObject clazz, IRubyObject arg0, Block block) { Parser parser = (Parser)((RubyClass)clazz).allocate(); - parser.callInit(args, block); + parser.callInit(arg0, block); + + return parser; + } + + @JRubyMethod(name = "new", meta = true) + public static IRubyObject newInstance(IRubyObject clazz, IRubyObject arg0, IRubyObject arg1, Block block) { + Parser parser = (Parser)((RubyClass)clazz).allocate(); + + parser.callInit(arg0, arg1, block); return parser; } @JRubyMethod(meta=true) public static IRubyObject parse(ThreadContext context, IRubyObject clazz, IRubyObject source, IRubyObject opts) { - IRubyObject[] args = new IRubyObject[] {source, opts}; Parser parser = (Parser)((RubyClass)clazz).allocate(); - parser.callInit(args, null); + parser.callInit(source, opts, null); return parser.parse(context); } - @JRubyMethod(required = 1, optional = 1, visibility = Visibility.PRIVATE) - public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { - Ruby runtime = context.getRuntime(); + @JRubyMethod(visibility = Visibility.PRIVATE) + public IRubyObject initialize(ThreadContext context, IRubyObject arg0) { + return initialize(context, arg0, null); + } + + @JRubyMethod(visibility = Visibility.PRIVATE) + public IRubyObject initialize(ThreadContext context, IRubyObject arg0, IRubyObject arg1) { + Ruby runtime = context.runtime; if (this.vSource != null) { throw runtime.newTypeError("already initialized instance"); - } + } - OptionsReader opts = new OptionsReader(context, args.length > 1 ? args[1] : null); + OptionsReader opts = new OptionsReader(context, arg1); this.maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING); this.allowNaN = opts.getBool("allow_nan", false); this.allowTrailingComma = opts.getBool("allow_trailing_comma", false); @@ -215,12 +226,9 @@ public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { } if(symbolizeNames && createAdditions) { - throw runtime.newArgumentError( - "options :symbolize_names and :create_additions cannot be " + - " used in conjunction" - ); + throw runtime.newArgumentError("options :symbolize_names and :create_additions cannot be used in conjunction"); } - this.vSource = args[0].convertToString(); + this.vSource = arg0.convertToString(); this.vSource = convertEncoding(context, vSource); return this; @@ -232,44 +240,17 @@ public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { * Returns the source string if no conversion is needed. */ private RubyString convertEncoding(ThreadContext context, RubyString source) { - RubyEncoding encoding = (RubyEncoding)source.encoding(context); - if (encoding == info.ascii8bit.get()) { + Encoding encoding = source.getEncoding(); + if (encoding == ASCIIEncoding.INSTANCE) { source = (RubyString) source.dup(); - source.force_encoding(context, info.utf8.get()); - } else { - source = (RubyString) source.encode(context, info.utf8.get()); + source.setEncoding(UTF8Encoding.INSTANCE); + source.clearCodeRange(); + } else if (encoding != UTF8Encoding.INSTANCE) { + source = (RubyString) source.encode(context, context.runtime.getEncodingService().convertEncodingToRubyEncoding(UTF8Encoding.INSTANCE)); } return source; } - /** - * Checks the first four bytes of the given ByteList to infer its encoding, - * using the principle demonstrated on section 3 of RFC 4627 (JSON). - */ - private static String sniffByteList(ByteList bl) { - if (bl.length() < 4) return null; - if (bl.get(0) == 0 && bl.get(2) == 0) { - return bl.get(1) == 0 ? "utf-32be" : "utf-16be"; - } - if (bl.get(1) == 0 && bl.get(3) == 0) { - return bl.get(2) == 0 ? "utf-32le" : "utf-16le"; - } - return null; - } - - /** - * Assumes the given (binary) RubyString to be in the given encoding, then - * converts it to UTF-8. - */ - private RubyString reinterpretEncoding(ThreadContext context, - RubyString str, String sniffedEncoding) { - RubyEncoding actualEncoding = info.getEncoding(context, sniffedEncoding); - RubyEncoding targetEncoding = info.utf8.get(); - RubyString dup = (RubyString)str.dup(); - dup.force_encoding(context, actualEncoding); - return (RubyString)dup.encode_bang(context, targetEncoding); - } - /** * Parser#parse() * @@ -278,7 +259,7 @@ private RubyString reinterpretEncoding(ThreadContext context, */ @JRubyMethod public IRubyObject parse(ThreadContext context) { - return new ParserSession(this, context, info).parse(); + return new ParserSession(this, context, info).parse(context); } /** @@ -288,15 +269,15 @@ public IRubyObject parse(ThreadContext context) { * used to construct this Parser. */ @JRubyMethod(name = "source") - public IRubyObject source_get() { - return checkAndGetSource().dup(); + public IRubyObject source_get(ThreadContext context) { + return checkAndGetSource(context).dup(); } - public RubyString checkAndGetSource() { + public RubyString checkAndGetSource(ThreadContext context) { if (vSource != null) { return vSource; } else { - throw getRuntime().newTypeError("uninitialized instance"); + throw context.runtime.newTypeError("uninitialized instance"); } } @@ -335,47 +316,35 @@ private IRubyObject createCustomDecimal(final ThreadContext context, final ByteL @SuppressWarnings("fallthrough") private static class ParserSession { private final Parser parser; - private final ThreadContext context; private final RuntimeInfo info; private final ByteList byteList; private final ByteList view; private final byte[] data; private final StringDecoder decoder; private int currentNesting = 0; - private final DoubleConverter dc; - - // initialization value for all state variables. - // no idea about the origins of this value, ask Flori ;) - private static final int EVIL = 0x666; private ParserSession(Parser parser, ThreadContext context, RuntimeInfo info) { this.parser = parser; - this.context = context; this.info = info; - this.byteList = parser.checkAndGetSource().getByteList(); + this.byteList = parser.checkAndGetSource(context).getByteList(); this.data = byteList.unsafeBytes(); this.view = new ByteList(data, false); - this.decoder = new StringDecoder(context); - this.dc = new DoubleConverter(); + this.decoder = new StringDecoder(); } - private RaiseException unexpectedToken(int absStart, int absEnd) { - RubyString msg = getRuntime().newString("unexpected token at '") + private RaiseException unexpectedToken(ThreadContext context, int absStart, int absEnd) { + RubyString msg = context.runtime.newString("unexpected token at '") .cat(data, absStart, Math.min(absEnd - absStart, 32)) .cat((byte)'\''); - return newException(Utils.M_PARSER_ERROR, msg); - } - - private Ruby getRuntime() { - return context.getRuntime(); + return newException(context, Utils.M_PARSER_ERROR, msg); } -// line 397 "Parser.rl" +// line 366 "Parser.rl" -// line 379 "Parser.java" +// line 348 "Parser.java" private static byte[] init__JSON_value_actions_0() { return new byte [] { @@ -489,22 +458,22 @@ private static byte[] init__JSON_value_from_state_actions_0() static final int JSON_value_en_main = 1; -// line 503 "Parser.rl" +// line 472 "Parser.rl" - void parseValue(ParserResult res, int p, int pe) { - int cs = EVIL; + void parseValue(ThreadContext context, ParserResult res, int p, int pe) { + int cs; IRubyObject result = null; -// line 501 "Parser.java" +// line 470 "Parser.java" { cs = JSON_value_start; } -// line 510 "Parser.rl" +// line 479 "Parser.rl" -// line 508 "Parser.java" +// line 477 "Parser.java" { int _klen; int _trans = 0; @@ -530,13 +499,13 @@ void parseValue(ParserResult res, int p, int pe) { while ( _nacts-- > 0 ) { switch ( _JSON_value_actions[_acts++] ) { case 9: -// line 488 "Parser.rl" +// line 457 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 540 "Parser.java" +// line 509 "Parser.java" } } @@ -599,45 +568,45 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) switch ( _JSON_value_actions[_acts++] ) { case 0: -// line 405 "Parser.rl" +// line 374 "Parser.rl" { - result = getRuntime().getNil(); + result = context.nil; } break; case 1: -// line 408 "Parser.rl" +// line 377 "Parser.rl" { - result = getRuntime().getFalse(); + result = context.fals; } break; case 2: -// line 411 "Parser.rl" +// line 380 "Parser.rl" { - result = getRuntime().getTrue(); + result = context.tru; } break; case 3: -// line 414 "Parser.rl" +// line 383 "Parser.rl" { if (parser.allowNaN) { result = getConstant(CONST_NAN); } else { - throw unexpectedToken(p - 2, pe); + throw unexpectedToken(context, p - 2, pe); } } break; case 4: -// line 421 "Parser.rl" +// line 390 "Parser.rl" { if (parser.allowNaN) { result = getConstant(CONST_INFINITY); } else { - throw unexpectedToken(p - 7, pe); + throw unexpectedToken(context, p - 7, pe); } } break; case 5: -// line 428 "Parser.rl" +// line 397 "Parser.rl" { if (pe > p + 8 && absSubSequence(p, p + 9).equals(JSON_MINUS_INFINITY)) { @@ -648,15 +617,15 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } else { - throw unexpectedToken(p, pe); + throw unexpectedToken(context, p, pe); } } - parseFloat(res, p, pe); + parseFloat(context, res, p, pe); if (res.result != null) { result = res.result; {p = (( res.p))-1;} } - parseInteger(res, p, pe); + parseInteger(context, res, p, pe); if (res.result != null) { result = res.result; {p = (( res.p))-1;} @@ -666,9 +635,9 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 6: -// line 454 "Parser.rl" +// line 423 "Parser.rl" { - parseString(res, p, pe); + parseString(context, res, p, pe); if (res.result == null) { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} @@ -679,10 +648,10 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 7: -// line 464 "Parser.rl" +// line 433 "Parser.rl" { currentNesting++; - parseArray(res, p, pe); + parseArray(context, res, p, pe); currentNesting--; if (res.result == null) { p--; @@ -694,10 +663,10 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 8: -// line 476 "Parser.rl" +// line 445 "Parser.rl" { currentNesting++; - parseObject(res, p, pe); + parseObject(context, res, p, pe); currentNesting--; if (res.result == null) { p--; @@ -708,7 +677,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } } break; -// line 712 "Parser.java" +// line 681 "Parser.java" } } } @@ -728,7 +697,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) break; } } -// line 511 "Parser.rl" +// line 480 "Parser.rl" if (cs >= JSON_value_first_final && result != null) { if (parser.freeze) { @@ -741,7 +710,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } -// line 745 "Parser.java" +// line 714 "Parser.java" private static byte[] init__JSON_integer_actions_0() { return new byte [] { @@ -840,33 +809,32 @@ private static byte[] init__JSON_integer_trans_actions_0() static final int JSON_integer_en_main = 1; -// line 533 "Parser.rl" +// line 502 "Parser.rl" - void parseInteger(ParserResult res, int p, int pe) { + void parseInteger(ThreadContext context, ParserResult res, int p, int pe) { int new_p = parseIntegerInternal(p, pe); if (new_p == -1) { res.update(null, p); return; } - RubyInteger number = createInteger(p, new_p); + RubyInteger number = createInteger(context, p, new_p); res.update(number, new_p + 1); - return; } int parseIntegerInternal(int p, int pe) { - int cs = EVIL; + int cs; -// line 862 "Parser.java" +// line 830 "Parser.java" { cs = JSON_integer_start; } -// line 550 "Parser.rl" +// line 518 "Parser.rl" int memo = p; -// line 870 "Parser.java" +// line 838 "Parser.java" { int _klen; int _trans = 0; @@ -947,13 +915,13 @@ else if ( data[p] > _JSON_integer_trans_keys[_mid+1] ) switch ( _JSON_integer_actions[_acts++] ) { case 0: -// line 527 "Parser.rl" +// line 496 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 957 "Parser.java" +// line 925 "Parser.java" } } } @@ -973,7 +941,7 @@ else if ( data[p] > _JSON_integer_trans_keys[_mid+1] ) break; } } -// line 552 "Parser.rl" +// line 520 "Parser.rl" if (cs < JSON_integer_first_final) { return -1; @@ -982,8 +950,8 @@ else if ( data[p] > _JSON_integer_trans_keys[_mid+1] ) return p; } - RubyInteger createInteger(int p, int new_p) { - Ruby runtime = getRuntime(); + RubyInteger createInteger(ThreadContext context, int p, int new_p) { + Ruby runtime = context.runtime; ByteList num = absSubSequence(p, new_p); return bytesToInum(runtime, num); } @@ -993,7 +961,7 @@ RubyInteger bytesToInum(Ruby runtime, ByteList num) { } -// line 997 "Parser.java" +// line 965 "Parser.java" private static byte[] init__JSON_float_actions_0() { return new byte [] { @@ -1095,10 +1063,10 @@ private static byte[] init__JSON_float_trans_actions_0() static final int JSON_float_en_main = 1; -// line 585 "Parser.rl" +// line 553 "Parser.rl" - void parseFloat(ParserResult res, int p, int pe) { + void parseFloat(ThreadContext context, ParserResult res, int p, int pe) { int new_p = parseFloatInternal(p, pe); if (new_p == -1) { res.update(null, p); @@ -1111,18 +1079,18 @@ void parseFloat(ParserResult res, int p, int pe) { } int parseFloatInternal(int p, int pe) { - int cs = EVIL; + int cs; -// line 1118 "Parser.java" +// line 1086 "Parser.java" { cs = JSON_float_start; } -// line 603 "Parser.rl" +// line 571 "Parser.rl" int memo = p; -// line 1126 "Parser.java" +// line 1094 "Parser.java" { int _klen; int _trans = 0; @@ -1203,13 +1171,13 @@ else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) switch ( _JSON_float_actions[_acts++] ) { case 0: -// line 576 "Parser.rl" +// line 544 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1213 "Parser.java" +// line 1181 "Parser.java" } } } @@ -1229,7 +1197,7 @@ else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) break; } } -// line 605 "Parser.rl" +// line 573 "Parser.rl" if (cs < JSON_float_first_final) { return -1; @@ -1239,7 +1207,7 @@ else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) } -// line 1243 "Parser.java" +// line 1211 "Parser.java" private static byte[] init__JSON_string_actions_0() { return new byte [] { @@ -1341,23 +1309,23 @@ private static byte[] init__JSON_string_trans_actions_0() static final int JSON_string_en_main = 1; -// line 644 "Parser.rl" +// line 612 "Parser.rl" - void parseString(ParserResult res, int p, int pe) { - int cs = EVIL; + void parseString(ThreadContext context, ParserResult res, int p, int pe) { + int cs; IRubyObject result = null; -// line 1353 "Parser.java" +// line 1321 "Parser.java" { cs = JSON_string_start; } -// line 651 "Parser.rl" +// line 619 "Parser.rl" int memo = p; -// line 1361 "Parser.java" +// line 1329 "Parser.java" { int _klen; int _trans = 0; @@ -1438,12 +1406,12 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) switch ( _JSON_string_actions[_acts++] ) { case 0: -// line 619 "Parser.rl" +// line 587 "Parser.rl" { int offset = byteList.begin(); - ByteList decoded = decoder.decode(byteList, memo + 1 - offset, + ByteList decoded = decoder.decode(context, byteList, memo + 1 - offset, p - offset); - result = getRuntime().newString(decoded); + result = context.runtime.newString(decoded); if (result == null) { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} @@ -1453,13 +1421,13 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) } break; case 1: -// line 632 "Parser.rl" +// line 600 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1463 "Parser.java" +// line 1431 "Parser.java" } } } @@ -1479,29 +1447,21 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) break; } } -// line 653 "Parser.rl" +// line 621 "Parser.rl" if (parser.createAdditions) { RubyHash matchString = parser.match_string; if (matchString != null) { final IRubyObject[] memoArray = { result, null }; try { - matchString.visitAll(new RubyHash.Visitor() { - @Override - public void visit(IRubyObject pattern, IRubyObject klass) { - if (pattern.callMethod(context, "===", memoArray[0]).isTrue()) { - memoArray[1] = klass; - throw JumpException.SPECIAL_JUMP; - } - } - }); + matchString.visitAll(context, MATCH_VISITOR, memoArray); } catch (JumpException e) { } if (memoArray[1] != null) { RubyClass klass = (RubyClass) memoArray[1]; if (klass.respondsTo("json_creatable?") && klass.callMethod(context, "json_creatable?").isTrue()) { if (parser.deprecatedCreateAdditions) { - klass.getRuntime().getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); + context.runtime.getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); } result = klass.callMethod(context, "json_create", result); } @@ -1512,10 +1472,11 @@ public void visit(IRubyObject pattern, IRubyObject klass) { if (cs >= JSON_string_first_final && result != null) { if (result instanceof RubyString) { RubyString string = (RubyString)result; - string.force_encoding(context, info.utf8.get()); + string.setEncoding(UTF8Encoding.INSTANCE); + string.clearCodeRange(); if (parser.freeze) { string.setFrozen(true); - string = getRuntime().freezeAndDedupString(string); + string = context.runtime.freezeAndDedupString(string); } res.update(string, p + 1); } else { @@ -1527,7 +1488,7 @@ public void visit(IRubyObject pattern, IRubyObject klass) { } -// line 1531 "Parser.java" +// line 1492 "Parser.java" private static byte[] init__JSON_array_actions_0() { return new byte [] { @@ -1694,34 +1655,34 @@ private static byte[] init__JSON_array_trans_actions_0() static final int JSON_array_en_main = 1; -// line 738 "Parser.rl" +// line 699 "Parser.rl" - void parseArray(ParserResult res, int p, int pe) { - int cs = EVIL; + void parseArray(ThreadContext context, ParserResult res, int p, int pe) { + int cs; if (parser.maxNesting > 0 && currentNesting > parser.maxNesting) { - throw newException(Utils.M_NESTING_ERROR, + throw newException(context, Utils.M_NESTING_ERROR, "nesting of " + currentNesting + " is too deep"); } IRubyObject result; - if (parser.arrayClass == getRuntime().getArray()) { - result = RubyArray.newArray(getRuntime()); + if (parser.arrayClass == context.runtime.getArray()) { + result = RubyArray.newArray(context.runtime); } else { result = parser.arrayClass.newInstance(context, IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); } -// line 1718 "Parser.java" +// line 1679 "Parser.java" { cs = JSON_array_start; } -// line 757 "Parser.rl" +// line 718 "Parser.rl" -// line 1725 "Parser.java" +// line 1686 "Parser.java" { int _klen; int _trans = 0; @@ -1764,7 +1725,7 @@ else if ( _widec > _JSON_array_cond_keys[_mid+1] ) case 0: { _widec = 65536 + (data[p] - 0); if ( -// line 705 "Parser.rl" +// line 666 "Parser.rl" parser.allowTrailingComma ) _widec += 65536; break; } @@ -1834,14 +1795,14 @@ else if ( _widec > _JSON_array_trans_keys[_mid+1] ) switch ( _JSON_array_actions[_acts++] ) { case 0: -// line 707 "Parser.rl" +// line 668 "Parser.rl" { - parseValue(res, p, pe); + parseValue(context, res, p, pe); if (res.result == null) { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } else { - if (parser.arrayClass == getRuntime().getArray()) { + if (parser.arrayClass == context.runtime.getArray()) { ((RubyArray)result).append(res.result); } else { result.callMethod(context, "<<", res.result); @@ -1851,13 +1812,13 @@ else if ( _widec > _JSON_array_trans_keys[_mid+1] ) } break; case 1: -// line 722 "Parser.rl" +// line 683 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1861 "Parser.java" +// line 1822 "Parser.java" } } } @@ -1877,17 +1838,17 @@ else if ( _widec > _JSON_array_trans_keys[_mid+1] ) break; } } -// line 758 "Parser.rl" +// line 719 "Parser.rl" if (cs >= JSON_array_first_final) { res.update(result, p + 1); } else { - throw unexpectedToken(p, pe); + throw unexpectedToken(context, p, pe); } } -// line 1891 "Parser.java" +// line 1852 "Parser.java" private static byte[] init__JSON_object_actions_0() { return new byte [] { @@ -2064,24 +2025,24 @@ private static byte[] init__JSON_object_trans_actions_0() static final int JSON_object_en_main = 1; -// line 819 "Parser.rl" +// line 780 "Parser.rl" - void parseObject(ParserResult res, int p, int pe) { - int cs = EVIL; + void parseObject(ThreadContext context, ParserResult res, int p, int pe) { + int cs; IRubyObject lastName = null; boolean objectDefault = true; if (parser.maxNesting > 0 && currentNesting > parser.maxNesting) { - throw newException(Utils.M_NESTING_ERROR, + throw newException(context, Utils.M_NESTING_ERROR, "nesting of " + currentNesting + " is too deep"); } // this is guaranteed to be a RubyHash due to the earlier // allocator test at OptionsReader#getClass IRubyObject result; - if (parser.objectClass == getRuntime().getHash()) { - result = RubyHash.newHash(getRuntime()); + if (parser.objectClass == context.runtime.getHash()) { + result = RubyHash.newHash(context.runtime); } else { objectDefault = false; result = parser.objectClass.newInstance(context, @@ -2089,14 +2050,14 @@ void parseObject(ParserResult res, int p, int pe) { } -// line 2093 "Parser.java" +// line 2054 "Parser.java" { cs = JSON_object_start; } -// line 843 "Parser.rl" +// line 804 "Parser.rl" -// line 2100 "Parser.java" +// line 2061 "Parser.java" { int _klen; int _trans = 0; @@ -2139,7 +2100,7 @@ else if ( _widec > _JSON_object_cond_keys[_mid+1] ) case 0: { _widec = 65536 + (data[p] - 0); if ( -// line 772 "Parser.rl" +// line 733 "Parser.rl" parser.allowTrailingComma ) _widec += 65536; break; } @@ -2209,26 +2170,26 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) switch ( _JSON_object_actions[_acts++] ) { case 0: -// line 774 "Parser.rl" +// line 735 "Parser.rl" { - parseValue(res, p, pe); + parseValue(context, res, p, pe); if (res.result == null) { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } else { - if (parser.objectClass == getRuntime().getHash()) { + if (parser.objectClass == context.runtime.getHash()) { ((RubyHash)result).op_aset(context, lastName, res.result); } else { - result.callMethod(context, "[]=", new IRubyObject[] { lastName, res.result }); + Helpers.invoke(context, result, "[]=", lastName, res.result); } {p = (( res.p))-1;} } } break; case 1: -// line 789 "Parser.rl" +// line 750 "Parser.rl" { - parseString(res, p, pe); + parseString(context, res, p, pe); if (res.result == null) { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} @@ -2244,13 +2205,13 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) } break; case 2: -// line 805 "Parser.rl" +// line 766 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 2254 "Parser.java" +// line 2215 "Parser.java" } } } @@ -2270,7 +2231,7 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) break; } } -// line 844 "Parser.rl" +// line 805 "Parser.rl" if (cs < JSON_object_first_final) { res.update(null, p + 1); @@ -2295,7 +2256,7 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) if (klass.respondsTo("json_creatable?") && klass.callMethod(context, "json_creatable?").isTrue()) { if (parser.deprecatedCreateAdditions) { - klass.getRuntime().getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); + context.runtime.getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); } returnedResult = klass.callMethod(context, "json_create", result); @@ -2306,7 +2267,7 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) } -// line 2310 "Parser.java" +// line 2271 "Parser.java" private static byte[] init__JSON_actions_0() { return new byte [] { @@ -2409,26 +2370,26 @@ private static byte[] init__JSON_trans_actions_0() static final int JSON_en_main = 1; -// line 898 "Parser.rl" +// line 859 "Parser.rl" - public IRubyObject parseImplemetation() { - int cs = EVIL; + public IRubyObject parseImplementation(ThreadContext context) { + int cs; int p, pe; IRubyObject result = null; ParserResult res = new ParserResult(); -// line 2423 "Parser.java" +// line 2384 "Parser.java" { cs = JSON_start; } -// line 907 "Parser.rl" +// line 868 "Parser.rl" p = byteList.begin(); pe = p + byteList.length(); -// line 2432 "Parser.java" +// line 2393 "Parser.java" { int _klen; int _trans = 0; @@ -2509,9 +2470,9 @@ else if ( data[p] > _JSON_trans_keys[_mid+1] ) switch ( _JSON_actions[_acts++] ) { case 0: -// line 884 "Parser.rl" +// line 845 "Parser.rl" { - parseValue(res, p, pe); + parseValue(context, res, p, pe); if (res.result == null) { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} @@ -2521,7 +2482,7 @@ else if ( data[p] > _JSON_trans_keys[_mid+1] ) } } break; -// line 2525 "Parser.java" +// line 2486 "Parser.java" } } } @@ -2541,23 +2502,23 @@ else if ( data[p] > _JSON_trans_keys[_mid+1] ) break; } } -// line 910 "Parser.rl" +// line 871 "Parser.rl" if (cs >= JSON_first_final && p == pe) { return result; } else { - throw unexpectedToken(p, pe); + throw unexpectedToken(context, p, pe); } } - public IRubyObject parse() { - return parseImplemetation(); + public IRubyObject parse(ThreadContext context) { + return parseImplementation(context); } /** * Updates the "view" bytelist with the new offsets and returns it. - * @param start - * @param end + * @param absStart + * @param absEnd */ private ByteList absSubSequence(int absStart, int absEnd) { view.setBegin(absStart); @@ -2573,18 +2534,22 @@ private IRubyObject getConstant(String name) { return parser.info.jsonModule.get().getConstant(name); } - private RaiseException newException(String className, String message) { + private RaiseException newException(ThreadContext context, String className, String message) { return Utils.newException(context, className, message); } - private RaiseException newException(String className, RubyString message) { + private RaiseException newException(ThreadContext context, String className, RubyString message) { return Utils.newException(context, className, message); } - private RaiseException newException(String className, - String messageBegin, ByteList messageEnd) { - return newException(className, - getRuntime().newString(messageBegin).cat(messageEnd)); - } + RubyHash.VisitorWithState MATCH_VISITOR = new RubyHash.VisitorWithState() { + @Override + public void visit(ThreadContext context, RubyHash self, IRubyObject pattern, IRubyObject klass, int index, IRubyObject[] state) { + if (pattern.callMethod(context, "===", state[0]).isTrue()) { + state[1] = klass; + throw JumpException.SPECIAL_JUMP; + } + } + }; } } diff --git a/java/src/json/ext/Parser.rl b/java/src/json/ext/Parser.rl index 9d2b96d6..bf42b445 100644 --- a/java/src/json/ext/Parser.rl +++ b/java/src/json/ext/Parser.rl @@ -5,10 +5,12 @@ */ package json.ext; +import org.jcodings.Encoding; +import org.jcodings.specific.ASCIIEncoding; +import org.jcodings.specific.UTF8Encoding; import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyClass; -import org.jruby.RubyEncoding; import org.jruby.RubyFloat; import org.jruby.RubyHash; import org.jruby.RubyInteger; @@ -18,13 +20,13 @@ import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.JumpException; import org.jruby.exceptions.RaiseException; import org.jruby.runtime.Block; +import org.jruby.runtime.Helpers; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.util.ByteList; import org.jruby.util.ConvertBytes; -import org.jruby.util.ConvertDouble; import java.util.function.BiFunction; @@ -39,7 +41,7 @@ import static org.jruby.util.ConvertDouble.DoubleConverter; * This is performed for you when you include "json/ext". * *

This class does not perform the actual parsing, just acts as an interface - * to Ruby code. When the {@link #parse()} method is invoked, a + * to Ruby code. When the {@link #parse(ThreadContext)} method is invoked, a * Parser.ParserSession object is instantiated, which handles the process. * * @author mernen @@ -69,11 +71,7 @@ public class Parser extends RubyObject { private static final String CONST_INFINITY = "Infinity"; private static final String CONST_MINUS_INFINITY = "MinusInfinity"; - static final ObjectAllocator ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klazz) { - return new Parser(runtime, klazz); - } - }; + static final ObjectAllocator ALLOCATOR = Parser::new; /** * Multiple-value return for internal parser methods. @@ -111,42 +109,42 @@ public class Parser extends RubyObject { * source. * It will be configured by the opts Hash. * opts can have the following keys: - * + *

*

*
:max_nesting *
The maximum depth of nesting allowed in the parsed data * structures. Disable depth checking with :max_nesting => false|nil|0, * it defaults to 100. - * + *

*

:allow_nan *
If set to true, allow NaN, * Infinity and -Infinity in defiance of RFC 4627 * to be parsed by the Parser. This option defaults to false. - * + *

*

:allow_trailing_comma *
If set to true, allow arrays and objects with a trailing * comma in defiance of RFC 4627 to be parsed by the Parser. * This option defaults to false. - * + *

*

:symbolize_names *
If set to true, returns symbols for the names (keys) in * a JSON object. Otherwise strings are returned, which is also the default. - * + *

*

:create_additions *
If set to false, the Parser doesn't create additions * even if a matching class and create_id was found. This option * defaults to true. - * + *

*

:object_class *
Defaults to Hash. If another type is provided, it will be used * instead of Hash to represent JSON objects. The type must respond to * new without arguments, and return an object that respond to []=. - * + *

*

:array_class *
Defaults to Array. If another type is provided, it will be used * instead of Hash to represent JSON arrays. The type must respond to * new without arguments, and return an object that respond to <<. - * + *

*

:decimal_class *
Specifies which class to use instead of the default (Float) when * parsing decimal numbers. This class must accept a single string argument @@ -154,31 +152,44 @@ public class Parser extends RubyObject { *
*/ - @JRubyMethod(name = "new", required = 1, optional = 1, meta = true) - public static IRubyObject newInstance(IRubyObject clazz, IRubyObject[] args, Block block) { + @JRubyMethod(name = "new", meta = true) + public static IRubyObject newInstance(IRubyObject clazz, IRubyObject arg0, Block block) { Parser parser = (Parser)((RubyClass)clazz).allocate(); - parser.callInit(args, block); + parser.callInit(arg0, block); + + return parser; + } + + @JRubyMethod(name = "new", meta = true) + public static IRubyObject newInstance(IRubyObject clazz, IRubyObject arg0, IRubyObject arg1, Block block) { + Parser parser = (Parser)((RubyClass)clazz).allocate(); + + parser.callInit(arg0, arg1, block); return parser; } @JRubyMethod(meta=true) public static IRubyObject parse(ThreadContext context, IRubyObject clazz, IRubyObject source, IRubyObject opts) { - IRubyObject[] args = new IRubyObject[] {source, opts}; Parser parser = (Parser)((RubyClass)clazz).allocate(); - parser.callInit(args, null); + parser.callInit(source, opts, null); return parser.parse(context); } - @JRubyMethod(required = 1, optional = 1, visibility = Visibility.PRIVATE) - public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { - Ruby runtime = context.getRuntime(); + @JRubyMethod(visibility = Visibility.PRIVATE) + public IRubyObject initialize(ThreadContext context, IRubyObject arg0) { + return initialize(context, arg0, null); + } + + @JRubyMethod(visibility = Visibility.PRIVATE) + public IRubyObject initialize(ThreadContext context, IRubyObject arg0, IRubyObject arg1) { + Ruby runtime = context.runtime; if (this.vSource != null) { throw runtime.newTypeError("already initialized instance"); - } + } - OptionsReader opts = new OptionsReader(context, args.length > 1 ? args[1] : null); + OptionsReader opts = new OptionsReader(context, arg1); this.maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING); this.allowNaN = opts.getBool("allow_nan", false); this.allowTrailingComma = opts.getBool("allow_trailing_comma", false); @@ -213,12 +224,9 @@ public class Parser extends RubyObject { } if(symbolizeNames && createAdditions) { - throw runtime.newArgumentError( - "options :symbolize_names and :create_additions cannot be " + - " used in conjunction" - ); + throw runtime.newArgumentError("options :symbolize_names and :create_additions cannot be used in conjunction"); } - this.vSource = args[0].convertToString(); + this.vSource = arg0.convertToString(); this.vSource = convertEncoding(context, vSource); return this; @@ -230,44 +238,17 @@ public class Parser extends RubyObject { * Returns the source string if no conversion is needed. */ private RubyString convertEncoding(ThreadContext context, RubyString source) { - RubyEncoding encoding = (RubyEncoding)source.encoding(context); - if (encoding == info.ascii8bit.get()) { + Encoding encoding = source.getEncoding(); + if (encoding == ASCIIEncoding.INSTANCE) { source = (RubyString) source.dup(); - source.force_encoding(context, info.utf8.get()); - } else { - source = (RubyString) source.encode(context, info.utf8.get()); + source.setEncoding(UTF8Encoding.INSTANCE); + source.clearCodeRange(); + } else if (encoding != UTF8Encoding.INSTANCE) { + source = (RubyString) source.encode(context, context.runtime.getEncodingService().convertEncodingToRubyEncoding(UTF8Encoding.INSTANCE)); } return source; } - /** - * Checks the first four bytes of the given ByteList to infer its encoding, - * using the principle demonstrated on section 3 of RFC 4627 (JSON). - */ - private static String sniffByteList(ByteList bl) { - if (bl.length() < 4) return null; - if (bl.get(0) == 0 && bl.get(2) == 0) { - return bl.get(1) == 0 ? "utf-32be" : "utf-16be"; - } - if (bl.get(1) == 0 && bl.get(3) == 0) { - return bl.get(2) == 0 ? "utf-32le" : "utf-16le"; - } - return null; - } - - /** - * Assumes the given (binary) RubyString to be in the given encoding, then - * converts it to UTF-8. - */ - private RubyString reinterpretEncoding(ThreadContext context, - RubyString str, String sniffedEncoding) { - RubyEncoding actualEncoding = info.getEncoding(context, sniffedEncoding); - RubyEncoding targetEncoding = info.utf8.get(); - RubyString dup = (RubyString)str.dup(); - dup.force_encoding(context, actualEncoding); - return (RubyString)dup.encode_bang(context, targetEncoding); - } - /** * Parser#parse() * @@ -276,7 +257,7 @@ public class Parser extends RubyObject { */ @JRubyMethod public IRubyObject parse(ThreadContext context) { - return new ParserSession(this, context, info).parse(); + return new ParserSession(this, context, info).parse(context); } /** @@ -286,15 +267,15 @@ public class Parser extends RubyObject { * used to construct this Parser. */ @JRubyMethod(name = "source") - public IRubyObject source_get() { - return checkAndGetSource().dup(); + public IRubyObject source_get(ThreadContext context) { + return checkAndGetSource(context).dup(); } - public RubyString checkAndGetSource() { + public RubyString checkAndGetSource(ThreadContext context) { if (vSource != null) { return vSource; } else { - throw getRuntime().newTypeError("uninitialized instance"); + throw context.runtime.newTypeError("uninitialized instance"); } } @@ -333,39 +314,27 @@ public class Parser extends RubyObject { @SuppressWarnings("fallthrough") private static class ParserSession { private final Parser parser; - private final ThreadContext context; private final RuntimeInfo info; private final ByteList byteList; private final ByteList view; private final byte[] data; private final StringDecoder decoder; private int currentNesting = 0; - private final DoubleConverter dc; - - // initialization value for all state variables. - // no idea about the origins of this value, ask Flori ;) - private static final int EVIL = 0x666; private ParserSession(Parser parser, ThreadContext context, RuntimeInfo info) { this.parser = parser; - this.context = context; this.info = info; - this.byteList = parser.checkAndGetSource().getByteList(); + this.byteList = parser.checkAndGetSource(context).getByteList(); this.data = byteList.unsafeBytes(); this.view = new ByteList(data, false); - this.decoder = new StringDecoder(context); - this.dc = new DoubleConverter(); + this.decoder = new StringDecoder(); } - private RaiseException unexpectedToken(int absStart, int absEnd) { - RubyString msg = getRuntime().newString("unexpected token at '") + private RaiseException unexpectedToken(ThreadContext context, int absStart, int absEnd) { + RubyString msg = context.runtime.newString("unexpected token at '") .cat(data, absStart, Math.min(absEnd - absStart, 32)) .cat((byte)'\''); - return newException(Utils.M_PARSER_ERROR, msg); - } - - private Ruby getRuntime() { - return context.getRuntime(); + return newException(context, Utils.M_PARSER_ERROR, msg); } %%{ @@ -403,26 +372,26 @@ public class Parser extends RubyObject { write data; action parse_null { - result = getRuntime().getNil(); + result = context.nil; } action parse_false { - result = getRuntime().getFalse(); + result = context.fals; } action parse_true { - result = getRuntime().getTrue(); + result = context.tru; } action parse_nan { if (parser.allowNaN) { result = getConstant(CONST_NAN); } else { - throw unexpectedToken(p - 2, pe); + throw unexpectedToken(context, p - 2, pe); } } action parse_infinity { if (parser.allowNaN) { result = getConstant(CONST_INFINITY); } else { - throw unexpectedToken(p - 7, pe); + throw unexpectedToken(context, p - 7, pe); } } action parse_number { @@ -435,15 +404,15 @@ public class Parser extends RubyObject { fhold; fbreak; } else { - throw unexpectedToken(p, pe); + throw unexpectedToken(context, p, pe); } } - parseFloat(res, fpc, pe); + parseFloat(context, res, fpc, pe); if (res.result != null) { result = res.result; fexec res.p; } - parseInteger(res, fpc, pe); + parseInteger(context, res, fpc, pe); if (res.result != null) { result = res.result; fexec res.p; @@ -452,7 +421,7 @@ public class Parser extends RubyObject { fbreak; } action parse_string { - parseString(res, fpc, pe); + parseString(context, res, fpc, pe); if (res.result == null) { fhold; fbreak; @@ -463,7 +432,7 @@ public class Parser extends RubyObject { } action parse_array { currentNesting++; - parseArray(res, fpc, pe); + parseArray(context, res, fpc, pe); currentNesting--; if (res.result == null) { fhold; @@ -475,7 +444,7 @@ public class Parser extends RubyObject { } action parse_object { currentNesting++; - parseObject(res, fpc, pe); + parseObject(context, res, fpc, pe); currentNesting--; if (res.result == null) { fhold; @@ -502,8 +471,8 @@ public class Parser extends RubyObject { ) %*exit; }%% - void parseValue(ParserResult res, int p, int pe) { - int cs = EVIL; + void parseValue(ThreadContext context, ParserResult res, int p, int pe) { + int cs; IRubyObject result = null; %% write init; @@ -532,19 +501,18 @@ public class Parser extends RubyObject { main := '-'? ( '0' | [1-9][0-9]* ) ( ^[0-9]? @exit ); }%% - void parseInteger(ParserResult res, int p, int pe) { + void parseInteger(ThreadContext context, ParserResult res, int p, int pe) { int new_p = parseIntegerInternal(p, pe); if (new_p == -1) { res.update(null, p); return; } - RubyInteger number = createInteger(p, new_p); + RubyInteger number = createInteger(context, p, new_p); res.update(number, new_p + 1); - return; } int parseIntegerInternal(int p, int pe) { - int cs = EVIL; + int cs; %% write init; int memo = p; @@ -557,8 +525,8 @@ public class Parser extends RubyObject { return p; } - RubyInteger createInteger(int p, int new_p) { - Ruby runtime = getRuntime(); + RubyInteger createInteger(ThreadContext context, int p, int new_p) { + Ruby runtime = context.runtime; ByteList num = absSubSequence(p, new_p); return bytesToInum(runtime, num); } @@ -584,7 +552,7 @@ public class Parser extends RubyObject { ( ^[0-9Ee.\-]? @exit ); }%% - void parseFloat(ParserResult res, int p, int pe) { + void parseFloat(ThreadContext context, ParserResult res, int p, int pe) { int new_p = parseFloatInternal(p, pe); if (new_p == -1) { res.update(null, p); @@ -597,7 +565,7 @@ public class Parser extends RubyObject { } int parseFloatInternal(int p, int pe) { - int cs = EVIL; + int cs; %% write init; int memo = p; @@ -618,9 +586,9 @@ public class Parser extends RubyObject { action parse_string { int offset = byteList.begin(); - ByteList decoded = decoder.decode(byteList, memo + 1 - offset, + ByteList decoded = decoder.decode(context, byteList, memo + 1 - offset, p - offset); - result = getRuntime().newString(decoded); + result = context.runtime.newString(decoded); if (result == null) { fhold; fbreak; @@ -643,8 +611,8 @@ public class Parser extends RubyObject { ) '"' @exit; }%% - void parseString(ParserResult res, int p, int pe) { - int cs = EVIL; + void parseString(ThreadContext context, ParserResult res, int p, int pe) { + int cs; IRubyObject result = null; %% write init; @@ -656,22 +624,14 @@ public class Parser extends RubyObject { if (matchString != null) { final IRubyObject[] memoArray = { result, null }; try { - matchString.visitAll(new RubyHash.Visitor() { - @Override - public void visit(IRubyObject pattern, IRubyObject klass) { - if (pattern.callMethod(context, "===", memoArray[0]).isTrue()) { - memoArray[1] = klass; - throw JumpException.SPECIAL_JUMP; - } - } - }); + matchString.visitAll(context, MATCH_VISITOR, memoArray); } catch (JumpException e) { } if (memoArray[1] != null) { RubyClass klass = (RubyClass) memoArray[1]; if (klass.respondsTo("json_creatable?") && klass.callMethod(context, "json_creatable?").isTrue()) { if (parser.deprecatedCreateAdditions) { - klass.getRuntime().getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); + context.runtime.getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); } result = klass.callMethod(context, "json_create", result); } @@ -682,10 +642,11 @@ public class Parser extends RubyObject { if (cs >= JSON_string_first_final && result != null) { if (result instanceof RubyString) { RubyString string = (RubyString)result; - string.force_encoding(context, info.utf8.get()); + string.setEncoding(UTF8Encoding.INSTANCE); + string.clearCodeRange(); if (parser.freeze) { string.setFrozen(true); - string = getRuntime().freezeAndDedupString(string); + string = context.runtime.freezeAndDedupString(string); } res.update(string, p + 1); } else { @@ -705,12 +666,12 @@ public class Parser extends RubyObject { action allow_trailing_comma { parser.allowTrailingComma } action parse_value { - parseValue(res, fpc, pe); + parseValue(context, res, fpc, pe); if (res.result == null) { fhold; fbreak; } else { - if (parser.arrayClass == getRuntime().getArray()) { + if (parser.arrayClass == context.runtime.getArray()) { ((RubyArray)result).append(res.result); } else { result.callMethod(context, "<<", res.result); @@ -737,17 +698,17 @@ public class Parser extends RubyObject { end_array @exit; }%% - void parseArray(ParserResult res, int p, int pe) { - int cs = EVIL; + void parseArray(ThreadContext context, ParserResult res, int p, int pe) { + int cs; if (parser.maxNesting > 0 && currentNesting > parser.maxNesting) { - throw newException(Utils.M_NESTING_ERROR, + throw newException(context, Utils.M_NESTING_ERROR, "nesting of " + currentNesting + " is too deep"); } IRubyObject result; - if (parser.arrayClass == getRuntime().getArray()) { - result = RubyArray.newArray(getRuntime()); + if (parser.arrayClass == context.runtime.getArray()) { + result = RubyArray.newArray(context.runtime); } else { result = parser.arrayClass.newInstance(context, IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); @@ -759,7 +720,7 @@ public class Parser extends RubyObject { if (cs >= JSON_array_first_final) { res.update(result, p + 1); } else { - throw unexpectedToken(p, pe); + throw unexpectedToken(context, p, pe); } } @@ -772,22 +733,22 @@ public class Parser extends RubyObject { action allow_trailing_comma { parser.allowTrailingComma } action parse_value { - parseValue(res, fpc, pe); + parseValue(context, res, fpc, pe); if (res.result == null) { fhold; fbreak; } else { - if (parser.objectClass == getRuntime().getHash()) { + if (parser.objectClass == context.runtime.getHash()) { ((RubyHash)result).op_aset(context, lastName, res.result); } else { - result.callMethod(context, "[]=", new IRubyObject[] { lastName, res.result }); + Helpers.invoke(context, result, "[]=", lastName, res.result); } fexec res.p; } } action parse_name { - parseString(res, fpc, pe); + parseString(context, res, fpc, pe); if (res.result == null) { fhold; fbreak; @@ -818,21 +779,21 @@ public class Parser extends RubyObject { ) @exit; }%% - void parseObject(ParserResult res, int p, int pe) { - int cs = EVIL; + void parseObject(ThreadContext context, ParserResult res, int p, int pe) { + int cs; IRubyObject lastName = null; boolean objectDefault = true; if (parser.maxNesting > 0 && currentNesting > parser.maxNesting) { - throw newException(Utils.M_NESTING_ERROR, + throw newException(context, Utils.M_NESTING_ERROR, "nesting of " + currentNesting + " is too deep"); } // this is guaranteed to be a RubyHash due to the earlier // allocator test at OptionsReader#getClass IRubyObject result; - if (parser.objectClass == getRuntime().getHash()) { - result = RubyHash.newHash(getRuntime()); + if (parser.objectClass == context.runtime.getHash()) { + result = RubyHash.newHash(context.runtime); } else { objectDefault = false; result = parser.objectClass.newInstance(context, @@ -865,7 +826,7 @@ public class Parser extends RubyObject { if (klass.respondsTo("json_creatable?") && klass.callMethod(context, "json_creatable?").isTrue()) { if (parser.deprecatedCreateAdditions) { - klass.getRuntime().getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); + context.runtime.getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); } returnedResult = klass.callMethod(context, "json_create", result); @@ -882,7 +843,7 @@ public class Parser extends RubyObject { write data; action parse_value { - parseValue(res, fpc, pe); + parseValue(context, res, fpc, pe); if (res.result == null) { fhold; fbreak; @@ -897,8 +858,8 @@ public class Parser extends RubyObject { ignore*; }%% - public IRubyObject parseImplemetation() { - int cs = EVIL; + public IRubyObject parseImplementation(ThreadContext context) { + int cs; int p, pe; IRubyObject result = null; ParserResult res = new ParserResult(); @@ -911,18 +872,18 @@ public class Parser extends RubyObject { if (cs >= JSON_first_final && p == pe) { return result; } else { - throw unexpectedToken(p, pe); + throw unexpectedToken(context, p, pe); } } - public IRubyObject parse() { - return parseImplemetation(); + public IRubyObject parse(ThreadContext context) { + return parseImplementation(context); } /** * Updates the "view" bytelist with the new offsets and returns it. - * @param start - * @param end + * @param absStart + * @param absEnd */ private ByteList absSubSequence(int absStart, int absEnd) { view.setBegin(absStart); @@ -938,18 +899,22 @@ public class Parser extends RubyObject { return parser.info.jsonModule.get().getConstant(name); } - private RaiseException newException(String className, String message) { + private RaiseException newException(ThreadContext context, String className, String message) { return Utils.newException(context, className, message); } - private RaiseException newException(String className, RubyString message) { + private RaiseException newException(ThreadContext context, String className, RubyString message) { return Utils.newException(context, className, message); } - private RaiseException newException(String className, - String messageBegin, ByteList messageEnd) { - return newException(className, - getRuntime().newString(messageBegin).cat(messageEnd)); - } + RubyHash.VisitorWithState MATCH_VISITOR = new RubyHash.VisitorWithState() { + @Override + public void visit(ThreadContext context, RubyHash self, IRubyObject pattern, IRubyObject klass, int index, IRubyObject[] state) { + if (pattern.callMethod(context, "===", state[0]).isTrue()) { + state[1] = klass; + throw JumpException.SPECIAL_JUMP; + } + } + }; } } diff --git a/java/src/json/ext/RuntimeInfo.java b/java/src/json/ext/RuntimeInfo.java index 2323bd94..f712dc25 100644 --- a/java/src/json/ext/RuntimeInfo.java +++ b/java/src/json/ext/RuntimeInfo.java @@ -6,12 +6,10 @@ package json.ext; import java.lang.ref.WeakReference; -import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; import org.jruby.Ruby; import org.jruby.RubyClass; -import org.jruby.RubyEncoding; import org.jruby.RubyModule; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; @@ -20,7 +18,7 @@ final class RuntimeInfo { // since the vast majority of cases runs just one runtime, // we optimize for that - private static WeakReference runtime1 = new WeakReference(null); + private static WeakReference runtime1 = new WeakReference<>(null); private static RuntimeInfo info1; // store remaining runtimes here (does not include runtime1) private static Map runtimes; @@ -30,32 +28,12 @@ final class RuntimeInfo { // the Ruby runtime object, which would cause memory leaks in the runtimes map above. /** JSON */ WeakReference jsonModule; - /** JSON::Ext::Generator::GeneratorMethods::String::Extend */ - WeakReference stringExtendModule; /** JSON::Ext::Generator::State */ WeakReference generatorStateClass; /** JSON::SAFE_STATE_PROTOTYPE */ WeakReference safeStatePrototype; - final WeakReference utf8; - final WeakReference ascii8bit; - // other encodings - private final Map> encodings; - - private RuntimeInfo(Ruby runtime) { - RubyClass encodingClass = runtime.getEncoding(); - if (encodingClass == null) { // 1.8 mode - utf8 = ascii8bit = null; - encodings = null; - } else { - ThreadContext context = runtime.getCurrentContext(); - - utf8 = new WeakReference((RubyEncoding)RubyEncoding.find(context, - encodingClass, runtime.newString("utf-8"))); - ascii8bit = new WeakReference((RubyEncoding)RubyEncoding.find(context, - encodingClass, runtime.newString("ascii-8bit"))); - encodings = new HashMap>(); - } + private RuntimeInfo() { } static RuntimeInfo initRuntime(Ruby runtime) { @@ -63,16 +41,16 @@ static RuntimeInfo initRuntime(Ruby runtime) { if (runtime1.get() == runtime) { return info1; } else if (runtime1.get() == null) { - runtime1 = new WeakReference(runtime); - info1 = new RuntimeInfo(runtime); + runtime1 = new WeakReference<>(runtime); + info1 = new RuntimeInfo(); return info1; } else { if (runtimes == null) { - runtimes = new WeakHashMap(1); + runtimes = new WeakHashMap<>(1); } RuntimeInfo cache = runtimes.get(runtime); if (cache == null) { - cache = new RuntimeInfo(runtime); + cache = new RuntimeInfo(); runtimes.put(runtime, cache); } return cache; @@ -90,26 +68,13 @@ public static RuntimeInfo forRuntime(Ruby runtime) { } } - public RubyEncoding getEncoding(ThreadContext context, String name) { - synchronized (encodings) { - WeakReference encoding = encodings.get(name); - if (encoding == null) { - Ruby runtime = context.getRuntime(); - encoding = new WeakReference((RubyEncoding)RubyEncoding.find(context, - runtime.getEncoding(), runtime.newString(name))); - encodings.put(name, encoding); - } - return encoding.get(); - } - } - public GeneratorState getSafeStatePrototype(ThreadContext context) { if (safeStatePrototype == null) { IRubyObject value = jsonModule.get().getConstant("SAFE_STATE_PROTOTYPE"); if (!(value instanceof GeneratorState)) { - throw context.getRuntime().newTypeError(value, generatorStateClass.get()); + throw context.runtime.newTypeError(value, generatorStateClass.get()); } - safeStatePrototype = new WeakReference((GeneratorState)value); + safeStatePrototype = new WeakReference<>((GeneratorState) value); } return safeStatePrototype.get(); } diff --git a/java/src/json/ext/StringDecoder.java b/java/src/json/ext/StringDecoder.java index f4877e93..bf616c1e 100644 --- a/java/src/json/ext/StringDecoder.java +++ b/java/src/json/ext/StringDecoder.java @@ -23,39 +23,37 @@ final class StringDecoder extends ByteListTranscoder { */ private int surrogatePairStart = -1; - // Array used for writing multi-byte characters into the buffer at once - private final byte[] aux = new byte[4]; + private ByteList out; - StringDecoder(ThreadContext context) { - super(context); - } + // Array used for writing multibyte characters into the buffer at once + private final byte[] aux = new byte[4]; - ByteList decode(ByteList src, int start, int end) { + ByteList decode(ThreadContext context, ByteList src, int start, int end) { try { - ByteListDirectOutputStream out = new ByteListDirectOutputStream(end - start); - init(src, start, end, out); + init(src, start, end); + this.out = new ByteList(end - start); while (hasNext()) { - handleChar(readUtf8Char()); + handleChar(context, readUtf8Char(context)); } quoteStop(pos); - return out.toByteListDirect(src.getEncoding()); + return out; } catch (IOException e) { throw context.runtime.newIOErrorFromException(e); } } - private void handleChar(int c) throws IOException { + private void handleChar(ThreadContext context, int c) throws IOException { if (c == '\\') { quoteStop(charStart); - handleEscapeSequence(); + handleEscapeSequence(context); } else { quoteStart(); } } - private void handleEscapeSequence() throws IOException { - ensureMin(1); - switch (readUtf8Char()) { + private void handleEscapeSequence(ThreadContext context) throws IOException { + ensureMin(context, 1); + switch (readUtf8Char(context)) { case 'b': append('\b'); break; @@ -72,13 +70,13 @@ private void handleEscapeSequence() throws IOException { append('\t'); break; case 'u': - ensureMin(4); - int cp = readHex(); + ensureMin(context, 4); + int cp = readHex(context); if (Character.isHighSurrogate((char)cp)) { - handleLowSurrogate((char)cp); + handleLowSurrogate(context, (char)cp); } else if (Character.isLowSurrogate((char)cp)) { // low surrogate with no high surrogate - throw invalidUtf8(); + throw invalidUtf8(context); } else { writeUtf8Char(cp); } @@ -88,15 +86,23 @@ private void handleEscapeSequence() throws IOException { } } - private void handleLowSurrogate(char highSurrogate) throws IOException { + protected void append(int b) throws IOException { + out.append(b); + } + + protected void append(byte[] origin, int start, int length) throws IOException { + out.append(origin, start, length); + } + + private void handleLowSurrogate(ThreadContext context, char highSurrogate) throws IOException { surrogatePairStart = charStart; - ensureMin(1); - int lowSurrogate = readUtf8Char(); + ensureMin(context, 1); + int lowSurrogate = readUtf8Char(context); if (lowSurrogate == '\\') { - ensureMin(5); - if (readUtf8Char() != 'u') throw invalidUtf8(); - lowSurrogate = readHex(); + ensureMin(context, 5); + if (readUtf8Char(context) != 'u') throw invalidUtf8(context); + lowSurrogate = readHex(context); } if (Character.isLowSurrogate((char)lowSurrogate)) { @@ -104,7 +110,7 @@ private void handleLowSurrogate(char highSurrogate) throws IOException { (char)lowSurrogate)); surrogatePairStart = -1; } else { - throw invalidUtf8(); + throw invalidUtf8(context); } } @@ -136,12 +142,12 @@ private byte tailByte(int value) { /** * Reads a 4-digit unsigned hexadecimal number from the source. */ - private int readHex() { + private int readHex(ThreadContext context) { int numberStart = pos; int result = 0; int length = 4; for (int i = 0; i < length; i++) { - int digit = readUtf8Char(); + int digit = readUtf8Char(context); int digitValue; if (digit >= '0' && digit <= '9') { digitValue = digit - '0'; @@ -159,13 +165,13 @@ private int readHex() { } @Override - protected RaiseException invalidUtf8() { + protected RaiseException invalidUtf8(ThreadContext context) { ByteList message = new ByteList( ByteList.plain("partial character in source, " + "but hit end near ")); int start = surrogatePairStart != -1 ? surrogatePairStart : charStart; message.append(src, start, srcEnd - start); return Utils.newException(context, Utils.M_PARSER_ERROR, - context.getRuntime().newString(message)); + context.runtime.newString(message)); } } diff --git a/java/src/json/ext/StringEncoder.java b/java/src/json/ext/StringEncoder.java index b1e7096e..63df459d 100644 --- a/java/src/json/ext/StringEncoder.java +++ b/java/src/json/ext/StringEncoder.java @@ -20,10 +20,12 @@ final class StringEncoder extends ByteListTranscoder { private final boolean asciiOnly, scriptSafe; + private OutputStream out; + // Escaped characters will reuse this array, to avoid new allocations // or appending them byte-by-byte private final byte[] aux = - new byte[] {/* First unicode character */ + new byte[] {/* First Unicode character */ '\\', 'u', 0, 0, 0, 0, /* Second unicode character (for surrogate pairs) */ '\\', 'u', 0, 0, 0, 0, @@ -40,22 +42,41 @@ final class StringEncoder extends ByteListTranscoder { new byte[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - StringEncoder(ThreadContext context, boolean asciiOnly, boolean scriptSafe) { - super(context); + StringEncoder(boolean asciiOnly, boolean scriptSafe) { this.asciiOnly = asciiOnly; this.scriptSafe = scriptSafe; } - void encode(ByteList src, OutputStream out) throws IOException { - init(src, out); + void encode(ThreadContext context, ByteList src, OutputStream out) throws IOException { + init(src); + this.out = out; + append('"'); + while (hasNext()) { + handleChar(readUtf8Char(context)); + } + quoteStop(pos); + append('"'); + } + + void encodeASCII(ThreadContext context, ByteList src, OutputStream out) throws IOException { + init(src); + this.out = out; append('"'); while (hasNext()) { - handleChar(readUtf8Char()); + handleChar(readASCIIChar()); } quoteStop(pos); append('"'); } + protected void append(int b) throws IOException { + out.write(b); + } + + protected void append(byte[] origin, int start, int length) throws IOException { + out.write(origin, start, length); + } + private void handleChar(int c) throws IOException { switch (c) { case '"': @@ -120,8 +141,7 @@ private void escapeCodeUnit(char c, int auxOffset) { } @Override - protected RaiseException invalidUtf8() { - return Utils.newException(context, Utils.M_GENERATOR_ERROR, - "source sequence is illegal/malformed utf-8"); + protected RaiseException invalidUtf8(ThreadContext context) { + return Utils.newException(context, Utils.M_GENERATOR_ERROR, "source sequence is illegal/malformed utf-8"); } } diff --git a/java/src/json/ext/Utils.java b/java/src/json/ext/Utils.java index ed6f8329..a33652d7 100644 --- a/java/src/json/ext/Utils.java +++ b/java/src/json/ext/Utils.java @@ -9,7 +9,6 @@ import org.jruby.RubyArray; import org.jruby.RubyClass; import org.jruby.RubyException; -import org.jruby.RubyHash; import org.jruby.RubyString; import org.jruby.exceptions.RaiseException; import org.jruby.runtime.Block; @@ -44,12 +43,6 @@ static RubyArray ensureArray(IRubyObject object) throws RaiseException { throw runtime.newTypeError(object, runtime.getArray()); } - static RubyHash ensureHash(IRubyObject object) throws RaiseException { - if (object instanceof RubyHash) return (RubyHash)object; - Ruby runtime = object.getRuntime(); - throw runtime.newTypeError(object, runtime.getHash()); - } - static RubyString ensureString(IRubyObject object) throws RaiseException { if (object instanceof RubyString) return (RubyString)object; Ruby runtime = object.getRuntime(); @@ -59,17 +52,15 @@ static RubyString ensureString(IRubyObject object) throws RaiseException { static RaiseException newException(ThreadContext context, String className, String message) { return newException(context, className, - context.getRuntime().newString(message)); + context.runtime.newString(message)); } static RaiseException newException(ThreadContext context, String className, RubyString message) { - RuntimeInfo info = RuntimeInfo.forRuntime(context.getRuntime()); + RuntimeInfo info = RuntimeInfo.forRuntime(context.runtime); RubyClass klazz = info.jsonModule.get().getClass(className); - RubyException excptn = - (RubyException)klazz.newInstance(context, - new IRubyObject[] {message}, Block.NULL_BLOCK); - return new RaiseException(excptn); + RubyException excptn = (RubyException)klazz.newInstance(context, message, Block.NULL_BLOCK); + return excptn.toThrowable(); } static byte[] repeat(ByteList a, int n) {