summaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
authorFlorian Frank <flori@ping.de>2010-09-22 22:21:02 +0200
committerFlorian Frank <flori@ping.de>2010-09-23 01:16:01 +0200
commite3fe104e7d5ec184aac36128aed2d217cb655dfc (patch)
tree3a63dc0152effdb990defcd5c935e38209649a8f /java
parent2c0f8d2c9b15a33b8d10ffcb1959aef54d320b57 (diff)
downloadjson-e3fe104e7d5ec184aac36128aed2d217cb655dfc.tar.gz
started to build jruby extension with Rakefile
Diffstat (limited to 'java')
-rw-r--r--java/lib/bytelist-1.0.6.jarbin0 -> 10493 bytes
-rw-r--r--java/lib/jcodings.jarbin0 -> 242327 bytes
-rw-r--r--java/src/json/ext/ByteListTranscoder.java167
-rw-r--r--java/src/json/ext/Generator.java441
-rw-r--r--java/src/json/ext/GeneratorMethods.java231
-rw-r--r--java/src/json/ext/GeneratorService.java42
-rw-r--r--java/src/json/ext/GeneratorState.java473
-rw-r--r--java/src/json/ext/OptionsReader.java108
-rw-r--r--java/src/json/ext/Parser.java2269
-rw-r--r--java/src/json/ext/Parser.rl799
-rw-r--r--java/src/json/ext/ParserService.java34
-rw-r--r--java/src/json/ext/RuntimeInfo.java119
-rw-r--r--java/src/json/ext/StringDecoder.java166
-rw-r--r--java/src/json/ext/StringEncoder.java106
-rw-r--r--java/src/json/ext/Utils.java89
15 files changed, 5044 insertions, 0 deletions
diff --git a/java/lib/bytelist-1.0.6.jar b/java/lib/bytelist-1.0.6.jar
new file mode 100644
index 0000000..7918e7c
--- /dev/null
+++ b/java/lib/bytelist-1.0.6.jar
Binary files differ
diff --git a/java/lib/jcodings.jar b/java/lib/jcodings.jar
new file mode 100644
index 0000000..e33fc99
--- /dev/null
+++ b/java/lib/jcodings.jar
Binary files differ
diff --git a/java/src/json/ext/ByteListTranscoder.java b/java/src/json/ext/ByteListTranscoder.java
new file mode 100644
index 0000000..ed9e54b
--- /dev/null
+++ b/java/src/json/ext/ByteListTranscoder.java
@@ -0,0 +1,167 @@
+/*
+ * This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
+ *
+ * Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
+ * for details.
+ */
+package json.ext;
+
+import org.jruby.exceptions.RaiseException;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.util.ByteList;
+
+/**
+ * A class specialized in transcoding a certain String format into another,
+ * 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 */
+ protected int charStart;
+ /** Position of the next character to read */
+ protected int pos;
+
+ private ByteList 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
+ * the sequence of characters that can be copied ends.
+ *
+ * <p>The variable stores -1 when not in a plain sequence.
+ */
+ private int quoteStart = -1;
+
+ protected ByteListTranscoder(ThreadContext context) {
+ this.context = context;
+ }
+
+ protected void init(ByteList src, ByteList out) {
+ this.init(src, 0, src.length(), out);
+ }
+
+ protected void init(ByteList src, int start, int end, ByteList out) {
+ this.src = src;
+ this.pos = start;
+ this.charStart = start;
+ this.srcEnd = end;
+ this.out = out;
+ }
+
+ /**
+ * Returns whether there are any characters left to be read.
+ */
+ protected boolean hasNext() {
+ return pos < srcEnd;
+ }
+
+ /**
+ * Returns the next character in the buffer.
+ */
+ private char next() {
+ return src.charAt(pos++);
+ }
+
+ /**
+ * Reads an UTF-8 character from the input and returns its code point,
+ * while advancing the input position.
+ *
+ * <p>Raises an {@link #invalidUtf8()} exception if an invalid byte
+ * is found.
+ */
+ protected int readUtf8Char() {
+ charStart = pos;
+ char head = next();
+ if (head <= 0x7f) { // 0b0xxxxxxx (ASCII)
+ return head;
+ }
+ if (head <= 0xbf) { // 0b10xxxxxx
+ throw invalidUtf8(); // tail byte with no head
+ }
+ if (head <= 0xdf) { // 0b110xxxxx
+ ensureMin(1);
+ int cp = ((head & 0x1f) << 6)
+ | nextPart();
+ if (cp < 0x0080) throw invalidUtf8();
+ return cp;
+ }
+ if (head <= 0xef) { // 0b1110xxxx
+ ensureMin(2);
+ int cp = ((head & 0x0f) << 12)
+ | (nextPart() << 6)
+ | nextPart();
+ if (cp < 0x0800) throw invalidUtf8();
+ return cp;
+ }
+ if (head <= 0xf7) { // 0b11110xxx
+ ensureMin(3);
+ int cp = ((head & 0x07) << 18)
+ | (nextPart() << 12)
+ | (nextPart() << 6)
+ | nextPart();
+ if (!Character.isValidCodePoint(cp)) throw invalidUtf8();
+ return cp;
+ }
+ // 0b11111xxx?
+ throw invalidUtf8();
+ }
+
+ /**
+ * 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();
+ }
+
+ /**
+ * Reads the next byte of a multi-byte UTF-8 character and returns its
+ * contents (lower 6 bits).
+ *
+ * <p>Throws a GeneratorError if the byte is not a valid tail.
+ */
+ private int nextPart() {
+ char c = next();
+ // tail bytes must be 0b10xxxxxx
+ if ((c & 0xc0) != 0x80) throw invalidUtf8();
+ return c & 0x3f;
+ }
+
+
+ protected void quoteStart() {
+ if (quoteStart == -1) quoteStart = charStart;
+ }
+
+ /**
+ * When in a sequence of characters that can be copied directly,
+ * interrupts the sequence and copies it to the output buffer.
+ *
+ * @param endPos The offset until which the direct character quoting should
+ * occur. You may pass {@link #pos} to quote until the most
+ * recently read character, or {@link #charStart} to quote
+ * until the character before it.
+ */
+ protected void quoteStop(int endPos) {
+ if (quoteStart != -1) {
+ out.append(src, quoteStart, endPos - quoteStart);
+ quoteStart = -1;
+ }
+ }
+
+ protected void append(int b) {
+ out.append(b);
+ }
+
+ protected void append(byte[] origin, int start, int length) {
+ out.append(origin, start, length);
+ }
+
+
+ protected abstract RaiseException invalidUtf8();
+
+ protected RaiseException incompleteUtf8() {
+ return invalidUtf8();
+ }
+}
diff --git a/java/src/json/ext/Generator.java b/java/src/json/ext/Generator.java
new file mode 100644
index 0000000..230d68f
--- /dev/null
+++ b/java/src/json/ext/Generator.java
@@ -0,0 +1,441 @@
+/*
+ * This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
+ *
+ * Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
+ * for details.
+ */
+package json.ext;
+
+import org.jruby.Ruby;
+import org.jruby.RubyArray;
+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.RubyNumeric;
+import org.jruby.RubyString;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+import org.jruby.util.ByteList;
+
+public final class Generator {
+ private Generator() {
+ throw new RuntimeException();
+ }
+
+ /**
+ * Encodes the given object as a JSON string, using the given handler.
+ */
+ static <T extends IRubyObject> RubyString
+ generateJson(ThreadContext context, T object,
+ Handler<? super T> handler, IRubyObject[] args) {
+ Session session = new Session(context, args.length > 0 ? args[0]
+ : null);
+ return session.infect(handler.generateNew(session, object));
+ }
+
+ /**
+ * Encodes the given object as a JSON string, detecting the appropriate handler
+ * for the given object.
+ */
+ static <T extends IRubyObject> RubyString
+ generateJson(ThreadContext context, T object, IRubyObject[] args) {
+ Handler<? super T> handler = getHandlerFor(context.getRuntime(), object);
+ return generateJson(context, object, handler, args);
+ }
+
+ /**
+ * Encodes the given object as a JSON string, using the appropriate
+ * handler if one is found or calling #to_json if not.
+ */
+ public static <T extends IRubyObject> RubyString
+ generateJson(ThreadContext context, T object,
+ GeneratorState config) {
+ Session session = new Session(context, config);
+ Handler<? super T> handler = getHandlerFor(context.getRuntime(), object);
+ return handler.generateNew(session, object);
+ }
+
+ /**
+ * Returns the best serialization handler for the given object.
+ */
+ // Java's generics can't handle this satisfactorily, so I'll just leave
+ // the best I could get and ignore the warnings
+ @SuppressWarnings("unchecked")
+ private static <T extends IRubyObject>
+ Handler<? super T> getHandlerFor(Ruby runtime, T object) {
+ RubyClass metaClass = object.getMetaClass();
+ if (metaClass == runtime.getString()) return (Handler)STRING_HANDLER;
+ if (metaClass == runtime.getFixnum()) return (Handler)FIXNUM_HANDLER;
+ if (metaClass == runtime.getHash()) return (Handler)HASH_HANDLER;
+ if (metaClass == runtime.getArray()) return (Handler)ARRAY_HANDLER;
+ if (object.isNil()) return (Handler)NIL_HANDLER;
+ if (object == runtime.getTrue()) return (Handler)TRUE_HANDLER;
+ if (object == runtime.getFalse()) return (Handler)FALSE_HANDLER;
+ if (metaClass == runtime.getFloat()) return (Handler)FLOAT_HANDLER;
+ if (metaClass == runtime.getBignum()) return (Handler)BIGNUM_HANDLER;
+ return GENERIC_HANDLER;
+ }
+
+
+ /* Generator context */
+
+ /**
+ * A class that concentrates all the information that is shared by
+ * generators working on a single session.
+ *
+ * <p>A session is defined as the process of serializing a single root
+ * object; any handler directly called by container handlers (arrays and
+ * hashes/objects) shares this object with its caller.
+ *
+ * <p>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;
+ this.state = state;
+ }
+
+ Session(ThreadContext context, IRubyObject possibleState) {
+ this.context = context;
+ this.possibleState = possibleState == null || possibleState.isNil()
+ ? null : possibleState;
+ }
+
+ public ThreadContext getContext() {
+ return context;
+ }
+
+ public Ruby getRuntime() {
+ return context.getRuntime();
+ }
+
+ public GeneratorState getState() {
+ if (state == null) {
+ state = GeneratorState.fromState(context, getInfo(), possibleState);
+ }
+ return state;
+ }
+
+ public RuntimeInfo getInfo() {
+ if (info == null) info = RuntimeInfo.forRuntime(getRuntime());
+ return info;
+ }
+
+ public StringEncoder getStringEncoder() {
+ if (stringEncoder == null) {
+ stringEncoder = new StringEncoder(context, getState().asciiOnly());
+ }
+ return stringEncoder;
+ }
+
+ public void infectBy(IRubyObject object) {
+ if (object.isTaint()) tainted = true;
+ if (object.isUntrusted()) untrusted = true;
+ }
+
+ public <T extends IRubyObject> T infect(T object) {
+ if (tainted) object.setTaint(true);
+ if (untrusted) object.setUntrusted(true);
+ return object;
+ }
+ }
+
+
+ /* Handler base classes */
+
+ private static abstract class Handler<T extends IRubyObject> {
+ /**
+ * Returns an estimative of how much space the serialization of the
+ * given object will take. Used for allocating enough buffer space
+ * before invoking other methods.
+ */
+ int guessSize(Session session, T object) {
+ return 4;
+ }
+
+ RubyString generateNew(Session session, T object) {
+ ByteList buffer = new ByteList(guessSize(session, object));
+ generate(session, object, buffer);
+ return RubyString.newString(session.getRuntime(), buffer);
+ }
+
+ abstract void generate(Session session, T object, ByteList buffer);
+ }
+
+ /**
+ * A handler that returns a fixed keyword regardless of the passed object.
+ */
+ private static class KeywordHandler<T extends IRubyObject>
+ extends Handler<T> {
+ private final ByteList keyword;
+
+ private KeywordHandler(String keyword) {
+ this.keyword = new ByteList(ByteList.plain(keyword), false);
+ }
+
+ @Override
+ int guessSize(Session session, T object) {
+ return keyword.length();
+ }
+
+ @Override
+ RubyString generateNew(Session session, T object) {
+ return RubyString.newStringShared(session.getRuntime(), keyword);
+ }
+
+ @Override
+ void generate(Session session, T object, ByteList buffer) {
+ buffer.append(keyword);
+ }
+ }
+
+
+ /* Handlers */
+
+ static final Handler<RubyBignum> BIGNUM_HANDLER =
+ new Handler<RubyBignum>() {
+ @Override
+ void generate(Session session, RubyBignum object, ByteList buffer) {
+ // JRUBY-4751: RubyBignum.to_s() returns generic object
+ // representation (fixed in 1.5, but we maintain backwards
+ // compatibility; call to_s(IRubyObject[]) then
+ buffer.append(((RubyString)object.to_s(IRubyObject.NULL_ARRAY)).getByteList());
+ }
+ };
+
+ static final Handler<RubyFixnum> FIXNUM_HANDLER =
+ new Handler<RubyFixnum>() {
+ @Override
+ void generate(Session session, RubyFixnum object, ByteList buffer) {
+ buffer.append(object.to_s().getByteList());
+ }
+ };
+
+ static final Handler<RubyFloat> FLOAT_HANDLER =
+ new Handler<RubyFloat>() {
+ @Override
+ void generate(Session session, RubyFloat object, ByteList buffer) {
+ double value = RubyFloat.num2dbl(object);
+
+ if (Double.isInfinite(value) || Double.isNaN(value)) {
+ if (!session.getState().allowNaN()) {
+ throw Utils.newException(session.getContext(),
+ Utils.M_GENERATOR_ERROR,
+ object + " not allowed in JSON");
+ }
+ }
+ buffer.append(((RubyString)object.to_s()).getByteList());
+ }
+ };
+
+ static final Handler<RubyArray> ARRAY_HANDLER =
+ new Handler<RubyArray>() {
+ @Override
+ int guessSize(Session session, RubyArray object) {
+ GeneratorState state = session.getState();
+ int depth = state.getDepth();
+ int perItem =
+ 4 // prealloc
+ + (depth + 1) * state.getIndent().length() // indent
+ + 1 + state.getArrayNl().length(); // ',' arrayNl
+ return 2 + object.size() * perItem;
+ }
+
+ @Override
+ void generate(Session session, RubyArray object, ByteList buffer) {
+ ThreadContext context = session.getContext();
+ Ruby runtime = context.getRuntime();
+ GeneratorState state = session.getState();
+ int depth = state.increaseDepth();
+
+ ByteList indentUnit = state.getIndent();
+ byte[] shift = Utils.repeat(indentUnit, depth);
+
+ ByteList arrayNl = state.getArrayNl();
+ byte[] delim = new byte[1 + arrayNl.length()];
+ delim[0] = ',';
+ System.arraycopy(arrayNl.unsafeBytes(), arrayNl.begin(), delim, 1,
+ arrayNl.length());
+
+ session.infectBy(object);
+
+ buffer.append((byte)'[');
+ buffer.append(arrayNl);
+ 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.append(delim);
+ }
+ buffer.append(shift);
+ Handler<IRubyObject> handler = getHandlerFor(runtime, element);
+ handler.generate(session, element, buffer);
+ }
+
+ state.decreaseDepth();
+ if (arrayNl.length() != 0) {
+ buffer.append(arrayNl);
+ buffer.append(shift, 0, state.getDepth() * indentUnit.length());
+ }
+
+ buffer.append((byte)']');
+ }
+ };
+
+ static final Handler<RubyHash> HASH_HANDLER =
+ new Handler<RubyHash>() {
+ @Override
+ int guessSize(Session session, RubyHash object) {
+ GeneratorState state = session.getState();
+ int perItem =
+ 12 // key, colon, comma
+ + (state.getDepth() + 1) * state.getIndent().length()
+ + state.getSpaceBefore().length()
+ + state.getSpace().length();
+ return 2 + object.size() * perItem;
+ }
+
+ @Override
+ void generate(final Session session, RubyHash object,
+ final ByteList buffer) {
+ ThreadContext context = session.getContext();
+ final Ruby runtime = context.getRuntime();
+ final GeneratorState state = session.getState();
+ final int depth = state.increaseDepth();
+
+ final ByteList objectNl = state.getObjectNl();
+ final byte[] indent = Utils.repeat(state.getIndent(), depth);
+ final ByteList spaceBefore = state.getSpaceBefore();
+ final ByteList space = state.getSpace();
+
+ buffer.append((byte)'{');
+ buffer.append(objectNl);
+ object.visitAll(new RubyHash.Visitor() {
+ private boolean firstPair = true;
+
+ @Override
+ public void visit(IRubyObject key, IRubyObject value) {
+ if (firstPair) {
+ firstPair = false;
+ } else {
+ buffer.append((byte)',');
+ buffer.append(objectNl);
+ }
+ if (objectNl.length() != 0) buffer.append(indent);
+
+ STRING_HANDLER.generate(session, key.asString(), buffer);
+ session.infectBy(key);
+
+ buffer.append(spaceBefore);
+ buffer.append((byte)':');
+ buffer.append(space);
+
+ Handler<IRubyObject> valueHandler = getHandlerFor(runtime, value);
+ valueHandler.generate(session, value, buffer);
+ session.infectBy(value);
+ }
+ });
+ state.decreaseDepth();
+ if (objectNl.length() != 0) {
+ buffer.append(objectNl);
+ if (indent.length != 0) {
+ for (int i = 0; i < state.getDepth(); i++) {
+ buffer.append(indent);
+ }
+ }
+ }
+ buffer.append((byte)'}');
+ }
+ };
+
+ static final Handler<RubyString> STRING_HANDLER =
+ new Handler<RubyString>() {
+ @Override
+ int guessSize(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
+ return 2 + object.getByteList().length();
+ }
+
+ @Override
+ void generate(Session session, RubyString object, ByteList buffer) {
+ RuntimeInfo info = session.getInfo();
+ RubyString src;
+
+ if (info.encodingsSupported() &&
+ object.encoding(session.getContext()) != info.utf8) {
+ src = (RubyString)object.encode(session.getContext(),
+ info.utf8);
+ } else {
+ src = object;
+ }
+
+ session.getStringEncoder().encode(src.getByteList(), buffer);
+ }
+ };
+
+ static final Handler<RubyBoolean> TRUE_HANDLER =
+ new KeywordHandler<RubyBoolean>("true");
+ static final Handler<RubyBoolean> FALSE_HANDLER =
+ new KeywordHandler<RubyBoolean>("false");
+ static final Handler<IRubyObject> NIL_HANDLER =
+ new KeywordHandler<IRubyObject>("null");
+
+ /**
+ * The default handler (<code>Object#to_json</code>): coerces the object
+ * to string using <code>#to_s</code>, and serializes that string.
+ */
+ static final Handler<IRubyObject> OBJECT_HANDLER =
+ new Handler<IRubyObject>() {
+ @Override
+ RubyString generateNew(Session session, IRubyObject object) {
+ RubyString str = object.asString();
+ return STRING_HANDLER.generateNew(session, str);
+ }
+
+ @Override
+ void generate(Session session, IRubyObject object, ByteList buffer) {
+ RubyString str = object.asString();
+ STRING_HANDLER.generate(session, str, buffer);
+ }
+ };
+
+ /**
+ * A handler that simply calls <code>#to_json(state)</code> on the
+ * given object.
+ */
+ static final Handler<IRubyObject> GENERIC_HANDLER =
+ new Handler<IRubyObject>() {
+ @Override
+ RubyString generateNew(Session session, IRubyObject object) {
+ IRubyObject result =
+ object.callMethod(session.getContext(), "to_json",
+ new IRubyObject[] {session.getState()});
+ if (result instanceof RubyString) return (RubyString)result;
+ throw session.getRuntime().newTypeError("to_json must return a String");
+ }
+
+ @Override
+ void generate(Session session, IRubyObject object, ByteList buffer) {
+ RubyString result = generateNew(session, object);
+ buffer.append(result.getByteList());
+ }
+ };
+}
diff --git a/java/src/json/ext/GeneratorMethods.java b/java/src/json/ext/GeneratorMethods.java
new file mode 100644
index 0000000..28a612d
--- /dev/null
+++ b/java/src/json/ext/GeneratorMethods.java
@@ -0,0 +1,231 @@
+/*
+ * This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
+ *
+ * Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
+ * for details.
+ */
+package json.ext;
+
+import org.jruby.Ruby;
+import org.jruby.RubyArray;
+import org.jruby.RubyBoolean;
+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;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+import org.jruby.util.ByteList;
+
+/**
+ * A class that populates the
+ * <code>Json::Ext::Generator::GeneratorMethods</code> module.
+ *
+ * @author mernen
+ */
+class GeneratorMethods {
+ /**
+ * Populates the given module with all modules and their methods
+ * @param info
+ * @param generatorMethodsModule The module to populate
+ * (normally <code>JSON::Generator::GeneratorMethods</code>)
+ */
+ static void populate(RuntimeInfo info, RubyModule module) {
+ defineMethods(module, "Array", RbArray.class);
+ defineMethods(module, "FalseClass", RbFalse.class);
+ defineMethods(module, "Float", RbFloat.class);
+ defineMethods(module, "Hash", RbHash.class);
+ defineMethods(module, "Integer", RbInteger.class);
+ defineMethods(module, "NilClass", RbNil.class);
+ defineMethods(module, "Object", RbObject.class);
+ defineMethods(module, "String", RbString.class);
+ defineMethods(module, "TrueClass", RbTrue.class);
+
+ info.stringExtendModule = module.defineModuleUnder("String")
+ .defineModuleUnder("Extend");
+ info.stringExtendModule.defineAnnotatedMethods(StringExtend.class);
+ }
+
+ /**
+ * Convenience method for defining methods on a submodule.
+ * @param parentModule
+ * @param submoduleName
+ * @param klass
+ */
+ private static void defineMethods(RubyModule parentModule,
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ public static class RbInteger {
+ @JRubyMethod(rest=true)
+ public static IRubyObject to_json(ThreadContext context,
+ IRubyObject vSelf, IRubyObject[] args) {
+ return Generator.generateJson(context, vSelf, args);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+
+ /**
+ * <code>{@link RubyString String}#to_json_raw(*)</code>
+ *
+ * <p>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) {
+ RubyHash obj = toJsonRawObject(context, Utils.ensureString(vSelf));
+ return Generator.generateJson(context, obj,
+ Generator.HASH_HANDLER, args);
+ }
+
+ /**
+ * <code>{@link RubyString String}#to_json_raw_object(*)</code>
+ *
+ * <p>This method creates a raw object Hash, that can be nested into
+ * other data structures and will be unparsed as a raw string. This
+ * 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) {
+ return toJsonRawObject(context, Utils.ensureString(vSelf));
+ }
+
+ private static RubyHash toJsonRawObject(ThreadContext context,
+ RubyString self) {
+ Ruby runtime = context.getRuntime();
+ RubyHash result = RubyHash.newHash(runtime);
+
+ IRubyObject createId = RuntimeInfo.forRuntime(runtime)
+ .jsonModule.callMethod(context, "create_id");
+ result.op_aset(context, createId, self.getMetaClass().to_s());
+
+ ByteList bl = self.getByteList();
+ byte[] uBytes = bl.unsafeBytes();
+ RubyArray array = runtime.newArray(bl.length());
+ for (int i = bl.begin(), t = bl.begin() + bl.length(); i < t; i++) {
+ array.store(i, runtime.newFixnum(uBytes[i] & 0xff));
+ }
+
+ result.op_aset(context, runtime.newString("raw"), array);
+ 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);
+ }
+ }
+
+ public static class StringExtend {
+ /**
+ * <code>{@link RubyString String}#json_create(o)</code>
+ *
+ * <p>Raw Strings are JSON Objects (the raw bytes are stored in an
+ * array for the key "raw"). The Ruby String can be created by this
+ * module method.
+ */
+ @JRubyMethod(required=1)
+ public static IRubyObject json_create(ThreadContext context,
+ IRubyObject vSelf, IRubyObject vHash) {
+ Ruby runtime = context.getRuntime();
+ RubyHash o = vHash.convertToHash();
+ IRubyObject rawData = o.fastARef(runtime.newString("raw"));
+ if (rawData == null) {
+ throw runtime.newArgumentError("\"raw\" value not defined "
+ + "for encoded String");
+ }
+ RubyArray ary = Utils.ensureArray(rawData);
+ byte[] bytes = new byte[ary.getLength()];
+ for (int i = 0, t = ary.getLength(); i < t; i++) {
+ IRubyObject element = ary.eltInternal(i);
+ if (element instanceof RubyFixnum) {
+ bytes[i] = (byte)RubyNumeric.fix2long(element);
+ } else {
+ throw runtime.newTypeError(element, runtime.getFixnum());
+ }
+ }
+ return runtime.newString(new ByteList(bytes, false));
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+}
diff --git a/java/src/json/ext/GeneratorService.java b/java/src/json/ext/GeneratorService.java
new file mode 100644
index 0000000..b8deb22
--- /dev/null
+++ b/java/src/json/ext/GeneratorService.java
@@ -0,0 +1,42 @@
+/*
+ * This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
+ *
+ * Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
+ * for details.
+ */
+package json.ext;
+
+import java.io.IOException;
+
+import org.jruby.Ruby;
+import org.jruby.RubyClass;
+import org.jruby.RubyModule;
+import org.jruby.runtime.load.BasicLibraryService;
+
+/**
+ * The service invoked by JRuby's {@link org.jruby.runtime.load.LoadService LoadService}.
+ * Defines the <code>JSON::Ext::Generator</code> module.
+ * @author mernen
+ */
+public class GeneratorService implements BasicLibraryService {
+ public boolean basicLoad(Ruby runtime) throws IOException {
+ runtime.getLoadService().require("json/common");
+ RuntimeInfo info = RuntimeInfo.initRuntime(runtime);
+
+ info.jsonModule = runtime.defineModule("JSON");
+ RubyModule jsonExtModule = info.jsonModule.defineModuleUnder("Ext");
+ RubyModule generatorModule = jsonExtModule.defineModuleUnder("Generator");
+
+ RubyClass stateClass =
+ generatorModule.defineClassUnder("State", runtime.getObject(),
+ GeneratorState.ALLOCATOR);
+ stateClass.defineAnnotatedMethods(GeneratorState.class);
+ info.generatorStateClass = stateClass;
+
+ RubyModule generatorMethods =
+ generatorModule.defineModuleUnder("GeneratorMethods");
+ GeneratorMethods.populate(info, generatorMethods);
+
+ return true;
+ }
+}
diff --git a/java/src/json/ext/GeneratorState.java b/java/src/json/ext/GeneratorState.java
new file mode 100644
index 0000000..dc99000
--- /dev/null
+++ b/java/src/json/ext/GeneratorState.java
@@ -0,0 +1,473 @@
+/*
+ * This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
+ *
+ * Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
+ * for details.
+ */
+package json.ext;
+
+import org.jruby.Ruby;
+import org.jruby.RubyBoolean;
+import org.jruby.RubyClass;
+import org.jruby.RubyHash;
+import org.jruby.RubyInteger;
+import org.jruby.RubyNumeric;
+import org.jruby.RubyObject;
+import org.jruby.RubyString;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.runtime.Block;
+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;
+
+/**
+ * The <code>JSON::Ext::Generator::State</code> class.
+ *
+ * <p>This class is used to create State instances, that are use to hold data
+ * while generating a JSON text from a a Ruby data structure.
+ *
+ * @author mernen
+ */
+public class GeneratorState extends RubyObject {
+ /**
+ * The indenting unit string. Will be repeated several times for larger
+ * indenting levels.
+ */
+ private ByteList indent = ByteList.EMPTY_BYTELIST;
+ /**
+ * The spacing to be added after a semicolon on a JSON object.
+ * @see #spaceBefore
+ */
+ private ByteList space = ByteList.EMPTY_BYTELIST;
+ /**
+ * The spacing to be added before a semicolon on a JSON object.
+ * @see #space
+ */
+ private ByteList spaceBefore = ByteList.EMPTY_BYTELIST;
+ /**
+ * Any suffix to be added after the comma for each element on a JSON object.
+ * It is assumed to be a newline, if set.
+ */
+ private ByteList objectNl = ByteList.EMPTY_BYTELIST;
+ /**
+ * Any suffix to be added after the comma for each element on a JSON Array.
+ * It is assumed to be a newline, if set.
+ */
+ private ByteList arrayNl = ByteList.EMPTY_BYTELIST;
+
+ /**
+ * The maximum level of nesting of structures allowed.
+ * <code>0</code> means disabled.
+ */
+ private int maxNesting = DEFAULT_MAX_NESTING;
+ static final int DEFAULT_MAX_NESTING = 19;
+ /**
+ * Whether special float values (<code>NaN</code>, <code>Infinity</code>,
+ * <code>-Infinity</code>) are accepted.
+ * If set to <code>false</code>, an exception will be thrown upon
+ * encountering one.
+ */
+ private boolean allowNaN = DEFAULT_ALLOW_NAN;
+ static final boolean DEFAULT_ALLOW_NAN = false;
+ /**
+ * XXX
+ */
+ private boolean asciiOnly = DEFAULT_ASCII_ONLY;
+ static final boolean DEFAULT_ASCII_ONLY = false;
+
+ /**
+ * The current depth (inside a #to_json call)
+ */
+ private int depth = 0;
+
+ static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
+ public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
+ return new GeneratorState(runtime, klazz);
+ }
+ };
+
+ public GeneratorState(Ruby runtime, RubyClass metaClass) {
+ super(runtime, metaClass);
+ }
+
+ /**
+ * <code>State.from_state(opts)</code>
+ *
+ * <p>Creates a State object from <code>opts</code>, which ought to be
+ * {@link RubyHash Hash} to create a new <code>State</code> instance
+ * configured by <codes>opts</code>, something else to create an
+ * unconfigured instance. If <code>opts</code> is a <code>State</code>
+ * object, it is just returned.
+ * @param clazzParam The receiver of the method call
+ * ({@link RubyClass} <code>State</code>)
+ * @param opts The object to use as a base for the new <code>State</code>
+ * @param block The block passed to the method
+ * @return A <code>GeneratorState</code> as determined above
+ */
+ @JRubyMethod(meta=true)
+ public static IRubyObject from_state(ThreadContext context,
+ IRubyObject klass, IRubyObject opts) {
+ return fromState(context, opts);
+ }
+
+ static GeneratorState fromState(ThreadContext context, IRubyObject opts) {
+ return fromState(context, RuntimeInfo.forRuntime(context.getRuntime()), opts);
+ }
+
+ static GeneratorState fromState(ThreadContext context, RuntimeInfo info,
+ IRubyObject opts) {
+ RubyClass klass = info.generatorStateClass;
+ if (opts != null) {
+ // if the given parameter is a Generator::State, return itself
+ 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);
+ }
+ }
+
+ // for other values, return the safe prototype
+ return (GeneratorState)info.getSafeStatePrototype(context).dup();
+ }
+
+ /**
+ * <code>State#initialize(opts = {})</code>
+ *
+ * Instantiates a new <code>State</code> object, configured by <code>opts</code>.
+ *
+ * <code>opts</code> can have the following keys:
+ *
+ * <dl>
+ * <dt><code>:indent</code>
+ * <dd>a {@link RubyString String} used to indent levels (default: <code>""</code>)
+ * <dt><code>:space</code>
+ * <dd>a String that is put after a <code>':'</code> or <code>','</code>
+ * delimiter (default: <code>""</code>)
+ * <dt><code>:space_before</code>
+ * <dd>a String that is put before a <code>":"</code> pair delimiter
+ * (default: <code>""</code>)
+ * <dt><code>:object_nl</code>
+ * <dd>a String that is put at the end of a JSON object (default: <code>""</code>)
+ * <dt><code>:array_nl</code>
+ * <dd>a String that is put at the end of a JSON array (default: <code>""</code>)
+ * <dt><code>:allow_nan</code>
+ * <dd><code>true</code> if <code>NaN</code>, <code>Infinity</code>, and
+ * <code>-Infinity</code> should be generated, otherwise an exception is
+ * thrown if these values are encountered.
+ * This options defaults to <code>false</code>.
+ */
+ @JRubyMethod(optional=1, visibility=Visibility.PRIVATE)
+ public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
+ configure(context, args.length > 0 ? args[0] : null);
+ return this;
+ }
+
+ @JRubyMethod
+ public IRubyObject initialize_copy(ThreadContext context, IRubyObject vOrig) {
+ Ruby runtime = context.getRuntime();
+ if (!(vOrig instanceof GeneratorState)) {
+ throw runtime.newTypeError(vOrig, getType());
+ }
+ GeneratorState orig = (GeneratorState)vOrig;
+ this.indent = orig.indent;
+ this.space = orig.space;
+ this.spaceBefore = orig.spaceBefore;
+ this.objectNl = orig.objectNl;
+ this.arrayNl = orig.arrayNl;
+ this.maxNesting = orig.maxNesting;
+ this.allowNaN = orig.allowNaN;
+ this.asciiOnly = orig.asciiOnly;
+ this.depth = orig.depth;
+ return this;
+ }
+
+ /**
+ * XXX
+ */
+ @JRubyMethod
+ public IRubyObject generate(ThreadContext context, IRubyObject obj) {
+ RubyString result = Generator.generateJson(context, obj, this);
+ if (!objectOrArrayLiteral(result)) {
+ throw Utils.newException(context, Utils.M_GENERATOR_ERROR,
+ "only generation of JSON objects or arrays allowed");
+ }
+ return result;
+ }
+
+ /**
+ * Ensures the given string is in the form "[...]" or "{...}", being
+ * possibly surrounded by white space.
+ * The string's encoding must be ASCII-compatible.
+ * @param value
+ * @return
+ */
+ private static boolean objectOrArrayLiteral(RubyString value) {
+ ByteList bl = value.getByteList();
+ int len = bl.length();
+
+ for (int pos = 0; pos < len - 1; pos++) {
+ int b = bl.get(pos);
+ if (Character.isWhitespace(b)) continue;
+
+ // match the opening brace
+ switch (b) {
+ case '[':
+ return matchClosingBrace(bl, pos, len, ']');
+ case '{':
+ return matchClosingBrace(bl, pos, len, '}');
+ default:
+ return false;
+ }
+ }
+ return false;
+ }
+
+ 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)
+ public IRubyObject op_aref(ThreadContext context, IRubyObject vName) {
+ String name = vName.asJavaString();
+ if (getMetaClass().isMethodBound(name, true)) {
+ return send(context, vName, Block.NULL_BLOCK);
+ }
+ return context.getRuntime().getNil();
+ }
+
+ public ByteList getIndent() {
+ return indent;
+ }
+
+ @JRubyMethod(name="indent")
+ public RubyString indent_get(ThreadContext context) {
+ return context.getRuntime().newString(indent);
+ }
+
+ @JRubyMethod(name="indent=")
+ public IRubyObject indent_set(ThreadContext context, IRubyObject indent) {
+ this.indent = prepareByteList(context, indent);
+ return indent;
+ }
+
+ public ByteList getSpace() {
+ return space;
+ }
+
+ @JRubyMethod(name="space")
+ public RubyString space_get(ThreadContext context) {
+ return context.getRuntime().newString(space);
+ }
+
+ @JRubyMethod(name="space=")
+ public IRubyObject space_set(ThreadContext context, IRubyObject space) {
+ this.space = prepareByteList(context, space);
+ return space;
+ }
+
+ public ByteList getSpaceBefore() {
+ return spaceBefore;
+ }
+
+ @JRubyMethod(name="space_before")
+ public RubyString space_before_get(ThreadContext context) {
+ return context.getRuntime().newString(spaceBefore);
+ }
+
+ @JRubyMethod(name="space_before=")
+ public IRubyObject space_before_set(ThreadContext context,
+ IRubyObject spaceBefore) {
+ this.spaceBefore = prepareByteList(context, spaceBefore);
+ return spaceBefore;
+ }
+
+ public ByteList getObjectNl() {
+ return objectNl;
+ }
+
+ @JRubyMethod(name="object_nl")
+ public RubyString object_nl_get(ThreadContext context) {
+ return context.getRuntime().newString(objectNl);
+ }
+
+ @JRubyMethod(name="object_nl=")
+ public IRubyObject object_nl_set(ThreadContext context,
+ IRubyObject objectNl) {
+ this.objectNl = prepareByteList(context, objectNl);
+ return objectNl;
+ }
+
+ public ByteList getArrayNl() {
+ return arrayNl;
+ }
+
+ @JRubyMethod(name="array_nl")
+ public RubyString array_nl_get(ThreadContext context) {
+ return context.getRuntime().newString(arrayNl);
+ }
+
+ @JRubyMethod(name="array_nl=")
+ public IRubyObject array_nl_set(ThreadContext context,
+ IRubyObject arrayNl) {
+ this.arrayNl = prepareByteList(context, arrayNl);
+ return arrayNl;
+ }
+
+ @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;
+ }
+
+ @JRubyMethod(name="max_nesting")
+ public RubyInteger max_nesting_get(ThreadContext context) {
+ return context.getRuntime().newFixnum(maxNesting);
+ }
+
+ @JRubyMethod(name="max_nesting=")
+ public IRubyObject max_nesting_set(IRubyObject max_nesting) {
+ maxNesting = RubyNumeric.fix2int(max_nesting);
+ return max_nesting;
+ }
+
+ public boolean allowNaN() {
+ return allowNaN;
+ }
+
+ @JRubyMethod(name="allow_nan?")
+ public RubyBoolean allow_nan_p(ThreadContext context) {
+ return context.getRuntime().newBoolean(allowNaN);
+ }
+
+ public boolean asciiOnly() {
+ return asciiOnly;
+ }
+
+ @JRubyMethod(name="ascii_only?")
+ public RubyBoolean ascii_only_p(ThreadContext context) {
+ return context.getRuntime().newBoolean(asciiOnly);
+ }
+
+ public int getDepth() {
+ return depth;
+ }
+
+ @JRubyMethod(name="depth")
+ public RubyInteger depth_get(ThreadContext context) {
+ return context.getRuntime().newFixnum(depth);
+ }
+
+ @JRubyMethod(name="depth=")
+ public IRubyObject depth_set(IRubyObject vDepth) {
+ depth = RubyNumeric.fix2int(vDepth);
+ return vDepth;
+ }
+
+ private ByteList prepareByteList(ThreadContext context, IRubyObject value) {
+ RubyString str = value.convertToString();
+ RuntimeInfo info = RuntimeInfo.forRuntime(context.getRuntime());
+ if (info.encodingsSupported() && str.encoding(context) != info.utf8) {
+ str = (RubyString)str.encode(context, info.utf8);
+ }
+ return str.getByteList().dup();
+ }
+
+ /**
+ * <code>State#configure(opts)</code>
+ *
+ * <p>Configures this State instance with the {@link RubyHash Hash}
+ * <code>opts</code>, and returns itself.
+ * @param vOpts The options hash
+ * @return The receiver
+ */
+ @JRubyMethod
+ public IRubyObject configure(ThreadContext context, IRubyObject vOpts) {
+ OptionsReader opts = new OptionsReader(context, vOpts);
+
+ ByteList indent = opts.getString("indent");
+ if (indent != null) this.indent = indent;
+
+ ByteList space = opts.getString("space");
+ if (space != null) this.space = space;
+
+ ByteList spaceBefore = opts.getString("space_before");
+ if (spaceBefore != null) this.spaceBefore = spaceBefore;
+
+ ByteList arrayNl = opts.getString("array_nl");
+ if (arrayNl != null) this.arrayNl = arrayNl;
+
+ ByteList objectNl = opts.getString("object_nl");
+ if (objectNl != null) this.objectNl = objectNl;
+
+ maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING);
+ allowNaN = opts.getBool("allow_nan", DEFAULT_ALLOW_NAN);
+ asciiOnly = opts.getBool("ascii_only", DEFAULT_ASCII_ONLY);
+
+ depth = opts.getInt("depth", 0);
+
+ return this;
+ }
+
+ /**
+ * <code>State#to_h()</code>
+ *
+ * <p>Returns the configuration instance variables as a hash, that can be
+ * passed to the configure method.
+ * @return
+ */
+ @JRubyMethod
+ public RubyHash to_h(ThreadContext context) {
+ Ruby runtime = context.getRuntime();
+ RubyHash result = RubyHash.newHash(runtime);
+
+ result.op_aset(context, runtime.newSymbol("indent"), indent_get(context));
+ result.op_aset(context, runtime.newSymbol("space"), space_get(context));
+ result.op_aset(context, runtime.newSymbol("space_before"), space_before_get(context));
+ result.op_aset(context, runtime.newSymbol("object_nl"), object_nl_get(context));
+ result.op_aset(context, runtime.newSymbol("array_nl"), array_nl_get(context));
+ result.op_aset(context, runtime.newSymbol("allow_nan"), allow_nan_p(context));
+ result.op_aset(context, runtime.newSymbol("ascii_only"), ascii_only_p(context));
+ result.op_aset(context, runtime.newSymbol("max_nesting"), max_nesting_get(context));
+ result.op_aset(context, runtime.newSymbol("depth"), depth_get(context));
+ return result;
+ }
+
+ public int increaseDepth() {
+ depth++;
+ checkMaxNesting();
+ return depth;
+ }
+
+ public int decreaseDepth() {
+ return --depth;
+ }
+
+ /**
+ * Checks if the current depth is allowed as per this state's options.
+ * @param context
+ * @param depth The corrent depth
+ */
+ private void checkMaxNesting() {
+ if (maxNesting != 0 && depth > maxNesting) {
+ depth--;
+ throw Utils.newException(getRuntime().getCurrentContext(),
+ 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
new file mode 100644
index 0000000..3bc8d5f
--- /dev/null
+++ b/java/src/json/ext/OptionsReader.java
@@ -0,0 +1,108 @@
+/*
+ * This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
+ *
+ * Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
+ * for details.
+ */
+package json.ext;
+
+import org.jruby.Ruby;
+import org.jruby.RubyClass;
+import org.jruby.RubyHash;
+import org.jruby.RubyNumeric;
+import org.jruby.RubyString;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+import org.jruby.util.ByteList;
+
+final class OptionsReader {
+ private final ThreadContext context;
+ private final Ruby runtime;
+ private final RubyHash opts;
+ private RuntimeInfo info;
+
+ OptionsReader(ThreadContext context, IRubyObject vOpts) {
+ this.context = context;
+ this.runtime = context.getRuntime();
+
+ if (vOpts == null || vOpts.isNil()) {
+ opts = null;
+ } else if (vOpts.respondsTo("to_hash")) {
+ opts = vOpts.convertToHash();
+ } else {
+ opts = vOpts.callMethod(context, "to_h").convertToHash();
+ }
+ }
+
+ private RuntimeInfo getRuntimeInfo() {
+ if (info != null) return info;
+ info = RuntimeInfo.forRuntime(runtime);
+ return info;
+ }
+
+ /**
+ * Efficiently looks up items with a {@link RubySymbol Symbol} key
+ * @param key The Symbol name to look up for
+ * @return The item in the {@link RubyHash Hash}, or <code>null</code>
+ * if not found
+ */
+ IRubyObject get(String key) {
+ return opts == null ? null : opts.fastARef(runtime.newSymbol(key));
+ }
+
+ boolean getBool(String key, boolean defaultValue) {
+ IRubyObject value = get(key);
+ return value == null ? defaultValue : value.isTrue();
+ }
+
+ int getInt(String key, int defaultValue) {
+ IRubyObject value = get(key);
+ if (value == null) return defaultValue;
+ if (!value.isTrue()) return 0;
+ return RubyNumeric.fix2int(value);
+ }
+
+ /**
+ * Reads the setting from the options hash. If no entry is set for this
+ * key or if it evaluates to <code>false</code>, returns null; attempts to
+ * coerce the value to {@link RubyString String} otherwise.
+ * @param key The Symbol name to look up for
+ * @return <code>null</code> if the key is not in the Hash or if
+ * its value evaluates to <code>false</code>
+ * @throws RaiseException <code>TypeError</code> if the value does not
+ * evaluate to <code>false</code> and can't be
+ * converted to string
+ */
+ ByteList getString(String key) {
+ IRubyObject value = get(key);
+ if (value == null || !value.isTrue()) return null;
+
+ RubyString str = value.convertToString();
+ RuntimeInfo info = getRuntimeInfo();
+ if (info.encodingsSupported() && str.encoding(context) != info.utf8) {
+ str = (RubyString)str.encode(context, info.utf8);
+ }
+ return str.getByteList().dup();
+ }
+
+ /**
+ * Reads the setting from the options hash. If it is <code>nil</code> or
+ * undefined, returns the default value given.
+ * If not, ensures it is a RubyClass instance and shares the same
+ * allocator as the default value (i.e. for the basic types which have
+ * their specific allocators, this ensures the passed value is
+ * a subclass of them).
+ */
+ RubyClass getClass(String key, RubyClass defaultValue) {
+ IRubyObject value = get(key);
+
+ if (value == null || value.isNil()) return defaultValue;
+
+ if (value instanceof RubyClass &&
+ ((RubyClass)value).getAllocator() == defaultValue.getAllocator()) {
+ return (RubyClass)value;
+ }
+ throw runtime.newTypeError(key + " option must be a subclass of "
+ + defaultValue);
+ }
+}
diff --git a/java/src/json/ext/Parser.java b/java/src/json/ext/Parser.java
new file mode 100644
index 0000000..c8f6f3d
--- /dev/null
+++ b/java/src/json/ext/Parser.java
@@ -0,0 +1,2269 @@
+
+// line 1 "Parser.rl"
+/*
+ * This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
+ *
+ * Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
+ * for details.
+ */
+package json.ext;
+
+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;
+import org.jruby.RubyModule;
+import org.jruby.RubyNumeric;
+import org.jruby.RubyObject;
+import org.jruby.RubyString;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.exceptions.RaiseException;
+import org.jruby.runtime.Block;
+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;
+
+/**
+ * The <code>JSON::Ext::Parser</code> class.
+ *
+ * <p>This is the JSON parser implemented as a Java class. To use it as the
+ * standard parser, set
+ * <pre>JSON.parser = JSON::Ext::Parser</pre>
+ * This is performed for you when you <code>include "json/ext"</code>.
+ *
+ * <p>This class does not perform the actual parsing, just acts as an interface
+ * to Ruby code. When the {@link #parse()} method is invoked, a
+ * Parser.ParserSession object is instantiated, which handles the process.
+ *
+ * @author mernen
+ */
+public class Parser extends RubyObject {
+ private final RuntimeInfo info;
+ private RubyString vSource;
+ private RubyString createId;
+ private int maxNesting;
+ private boolean allowNaN;
+ private boolean symbolizeNames;
+ private RubyClass objectClass;
+ private RubyClass arrayClass;
+
+ private static final int DEFAULT_MAX_NESTING = 19;
+
+ private static final String JSON_MINUS_INFINITY = "-Infinity";
+ // constant names in the JSON module containing those values
+ private static final String CONST_NAN = "NaN";
+ 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);
+ }
+ };
+
+ /**
+ * Multiple-value return for internal parser methods.
+ *
+ * <p>All the <code>parse<var>Stuff</var></code> methods return instances of
+ * <code>ParserResult</code> when successful, or <code>null</code> when
+ * there's a problem with the input data.
+ */
+ static final class ParserResult {
+ /**
+ * The result of the successful parsing. Should never be
+ * <code>null</code>.
+ */
+ final IRubyObject result;
+ /**
+ * The point where the parser returned.
+ */
+ final int p;
+
+ ParserResult(IRubyObject result, int p) {
+ this.result = result;
+ this.p = p;
+ }
+ }
+
+ public Parser(Ruby runtime, RubyClass metaClass) {
+ super(runtime, metaClass);
+ info = RuntimeInfo.forRuntime(runtime);
+ }
+
+ /**
+ * <code>Parser.new(source, opts = {})</code>
+ *
+ * <p>Creates a new <code>JSON::Ext::Parser</code> instance for the string
+ * <code>source</code>.
+ * It will be configured by the <code>opts</code> Hash.
+ * <code>opts</code> can have the following keys:
+ *
+ * <dl>
+ * <dt><code>:max_nesting</code>
+ * <dd>The maximum depth of nesting allowed in the parsed data
+ * structures. Disable depth checking with <code>:max_nesting => false|nil|0</code>,
+ * it defaults to 19.
+ *
+ * <dt><code>:allow_nan</code>
+ * <dd>If set to <code>true</code>, allow <code>NaN</code>,
+ * <code>Infinity</code> and <code>-Infinity</code> in defiance of RFC 4627
+ * to be parsed by the Parser. This option defaults to <code>false</code>.
+ *
+ * <dt><code>:symbolize_names</code>
+ * <dd>If set to <code>true</code>, returns symbols for the names (keys) in
+ * a JSON object. Otherwise strings are returned, which is also the default.
+ *
+ * <dt><code>:create_additions</code>
+ * <dd>If set to <code>false</code>, the Parser doesn't create additions
+ * even if a matchin class and <code>create_id</code> was found. This option
+ * defaults to <code>true</code>.
+ *
+ * <dt><code>:object_class</code>
+ * <dd>Defaults to Hash.
+ *
+ * <dt><code>:array_class</code>
+ * <dd>Defaults to Array.
+ * </dl>
+ */
+ @JRubyMethod(name = "new", required = 1, optional = 1, meta = true)
+ public static IRubyObject newInstance(IRubyObject clazz, IRubyObject[] args, Block block) {
+ Parser parser = (Parser)((RubyClass)clazz).allocate();
+
+ parser.callInit(args, block);
+
+ return parser;
+ }
+
+ @JRubyMethod(required = 1, optional = 1, visibility = Visibility.PRIVATE)
+ public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
+ Ruby runtime = context.getRuntime();
+ RubyString source = convertEncoding(context, args[0].convertToString());
+
+ OptionsReader opts =
+ new OptionsReader(context, args.length > 1 ? args[1] : null);
+
+ this.maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING);
+ this.allowNaN = opts.getBool("allow_nan", false);
+ this.symbolizeNames = opts.getBool("symbolize_names", false);
+ this.createId =
+ opts.getBool("create_additions", true) ? getCreateId(context)
+ : null;
+ this.objectClass = opts.getClass("object_class", runtime.getHash());
+ this.arrayClass = opts.getClass("array_class", runtime.getArray());
+
+ this.vSource = source;
+ return this;
+ }
+
+ /**
+ * Checks the given string's encoding. If a non-UTF-8 encoding is detected,
+ * a converted copy is returned.
+ * Returns the source string if no conversion is needed.
+ */
+ private RubyString convertEncoding(ThreadContext context, RubyString source) {
+ ByteList bl = source.getByteList();
+ int len = bl.length();
+ if (len < 2) {
+ throw Utils.newException(context, Utils.M_PARSER_ERROR,
+ "A JSON text must at least contain two octets!");
+ }
+
+ if (info.encodingsSupported()) {
+ RubyEncoding encoding = (RubyEncoding)source.encoding(context);
+ if (encoding != info.ascii8bit) {
+ return (RubyString)source.encode(context, info.utf8);
+ }
+
+ String sniffedEncoding = sniffByteList(bl);
+ if (sniffedEncoding == null) return source; // assume UTF-8
+ return reinterpretEncoding(context, source, sniffedEncoding);
+ }
+
+ String sniffedEncoding = sniffByteList(bl);
+ if (sniffedEncoding == null) return source; // assume UTF-8
+ Ruby runtime = context.getRuntime();
+ return (RubyString)info.jsonModule.
+ callMethod(context, "iconv",
+ new IRubyObject[] {
+ runtime.newString("utf-8"),
+ runtime.newString(sniffedEncoding),
+ 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;
+ RubyString dup = (RubyString)str.dup();
+ dup.force_encoding(context, actualEncoding);
+ return (RubyString)dup.encode_bang(context, targetEncoding);
+ }
+
+ /**
+ * <code>Parser#parse()</code>
+ *
+ * <p>Parses the current JSON text <code>source</code> and returns the
+ * complete data structure as a result.
+ */
+ @JRubyMethod
+ public IRubyObject parse(ThreadContext context) {
+ return new ParserSession(this, context).parse();
+ }
+
+ /**
+ * <code>Parser#source()</code>
+ *
+ * <p>Returns a copy of the current <code>source</code> string, that was
+ * used to construct this Parser.
+ */
+ @JRubyMethod(name = "source")
+ public IRubyObject source_get() {
+ return vSource.dup();
+ }
+
+ /**
+ * Queries <code>JSON.create_id</code>. Returns <code>null</code> if it is
+ * set to <code>nil</code> or <code>false</code>, and a String if not.
+ */
+ private RubyString getCreateId(ThreadContext context) {
+ IRubyObject v = info.jsonModule.callMethod(context, "create_id");
+ return v.isTrue() ? v.convertToString() : null;
+ }
+
+ /**
+ * A string parsing session.
+ *
+ * <p>Once a ParserSession is instantiated, the source string should not
+ * change until the parsing is complete. The ParserSession object assumes
+ * the source {@link RubyString} is still associated to its original
+ * {@link ByteList}, which in turn must still be bound to the same
+ * <code>byte[]</code> value (and on the same offset).
+ */
+ // Ragel uses lots of fall-through
+ @SuppressWarnings("fallthrough")
+ private static class ParserSession {
+ private final Parser parser;
+ private final ThreadContext context;
+ private final ByteList byteList;
+ private final byte[] data;
+ private final StringDecoder decoder;
+ private int currentNesting = 0;
+
+ // 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) {
+ this.parser = parser;
+ this.context = context;
+ this.byteList = parser.vSource.getByteList();
+ this.data = byteList.unsafeBytes();
+ this.decoder = new StringDecoder(context);
+ }
+
+ private RaiseException unexpectedToken(int absStart, int absEnd) {
+ RubyString msg = getRuntime().newString("unexpected token at '")
+ .cat(data, absStart, absEnd - absStart)
+ .cat((byte)'\'');
+ return newException(Utils.M_PARSER_ERROR, msg);
+ }
+
+ private Ruby getRuntime() {
+ return context.getRuntime();
+ }
+
+
+// line 323 "Parser.rl"
+
+
+
+// line 305 "Parser.java"
+private static byte[] init__JSON_value_actions_0()
+{
+ return new byte [] {
+ 0, 1, 0, 1, 1, 1, 2, 1, 3, 1, 4, 1,
+ 5, 1, 6, 1, 7, 1, 8, 1, 9
+ };
+}
+
+private static final byte _JSON_value_actions[] = init__JSON_value_actions_0();
+
+
+private static byte[] init__JSON_value_key_offsets_0()
+{
+ return new byte [] {
+ 0, 0, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
+ 21, 22, 23, 24, 25, 26, 27, 28, 29, 30
+ };
+}
+
+private static final byte _JSON_value_key_offsets[] = init__JSON_value_key_offsets_0();
+
+
+private static char[] init__JSON_value_trans_keys_0()
+{
+ return new char [] {
+ 34, 45, 73, 78, 91, 102, 110, 116, 123, 48, 57, 110,
+ 102, 105, 110, 105, 116, 121, 97, 78, 97, 108, 115, 101,
+ 117, 108, 108, 114, 117, 101, 0
+ };
+}
+
+private static final char _JSON_value_trans_keys[] = init__JSON_value_trans_keys_0();
+
+
+private static byte[] init__JSON_value_single_lengths_0()
+{
+ return new byte [] {
+ 0, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 0
+ };
+}
+
+private static final byte _JSON_value_single_lengths[] = init__JSON_value_single_lengths_0();
+
+
+private static byte[] init__JSON_value_range_lengths_0()
+{
+ return new byte [] {
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ };
+}
+
+private static final byte _JSON_value_range_lengths[] = init__JSON_value_range_lengths_0();
+
+
+private static byte[] init__JSON_value_index_offsets_0()
+{
+ return new byte [] {
+ 0, 0, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29,
+ 31, 33, 35, 37, 39, 41, 43, 45, 47, 49
+ };
+}
+
+private static final byte _JSON_value_index_offsets[] = init__JSON_value_index_offsets_0();
+
+
+private static byte[] init__JSON_value_trans_targs_0()
+{
+ return new byte [] {
+ 21, 21, 2, 9, 21, 11, 15, 18, 21, 21, 0, 3,
+ 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 21,
+ 0, 10, 0, 21, 0, 12, 0, 13, 0, 14, 0, 21,
+ 0, 16, 0, 17, 0, 21, 0, 19, 0, 20, 0, 21,
+ 0, 0, 0
+ };
+}
+
+private static final byte _JSON_value_trans_targs[] = init__JSON_value_trans_targs_0();
+
+
+private static byte[] init__JSON_value_trans_actions_0()
+{
+ return new byte [] {
+ 13, 11, 0, 0, 15, 0, 0, 0, 17, 11, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9,
+ 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 5,
+ 0, 0, 0
+ };
+}
+
+private static final byte _JSON_value_trans_actions[] = init__JSON_value_trans_actions_0();
+
+
+private static byte[] init__JSON_value_from_state_actions_0()
+{
+ return new byte [] {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 19
+ };
+}
+
+private static final byte _JSON_value_from_state_actions[] = init__JSON_value_from_state_actions_0();
+
+
+static final int JSON_value_start = 1;
+static final int JSON_value_first_final = 21;
+static final int JSON_value_error = 0;
+
+static final int JSON_value_en_main = 1;
+
+
+// line 429 "Parser.rl"
+
+
+ ParserResult parseValue(int p, int pe) {
+ int cs = EVIL;
+ IRubyObject result = null;
+
+
+// line 427 "Parser.java"
+ {
+ cs = JSON_value_start;
+ }
+
+// line 436 "Parser.rl"
+
+// line 434 "Parser.java"
+ {
+ int _klen;
+ int _trans = 0;
+ int _acts;
+ int _nacts;
+ int _keys;
+ int _goto_targ = 0;
+
+ _goto: while (true) {
+ switch ( _goto_targ ) {
+ case 0:
+ if ( p == pe ) {
+ _goto_targ = 4;
+ continue _goto;
+ }
+ if ( cs == 0 ) {
+ _goto_targ = 5;
+ continue _goto;
+ }
+case 1:
+ _acts = _JSON_value_from_state_actions[cs];
+ _nacts = (int) _JSON_value_actions[_acts++];
+ while ( _nacts-- > 0 ) {
+ switch ( _JSON_value_actions[_acts++] ) {
+ case 9:
+// line 414 "Parser.rl"
+ {
+ p--;
+ { p += 1; _goto_targ = 5; if (true) continue _goto;}
+ }
+ break;
+// line 466 "Parser.java"
+ }
+ }
+
+ _match: do {
+ _keys = _JSON_value_key_offsets[cs];
+ _trans = _JSON_value_index_offsets[cs];
+ _klen = _JSON_value_single_lengths[cs];
+ if ( _klen > 0 ) {
+ int _lower = _keys;
+ int _mid;
+ int _upper = _keys + _klen - 1;
+ while (true) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + ((_upper-_lower) >> 1);
+ if ( data[p] < _JSON_value_trans_keys[_mid] )
+ _upper = _mid - 1;
+ else if ( data[p] > _JSON_value_trans_keys[_mid] )
+ _lower = _mid + 1;
+ else {
+ _trans += (_mid - _keys);
+ break _match;
+ }
+ }
+ _keys += _klen;
+ _trans += _klen;
+ }
+
+ _klen = _JSON_value_range_lengths[cs];
+ if ( _klen > 0 ) {
+ int _lower = _keys;
+ int _mid;
+ int _upper = _keys + (_klen<<1) - 2;
+ while (true) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + (((_upper-_lower) >> 1) & ~1);
+ if ( data[p] < _JSON_value_trans_keys[_mid] )
+ _upper = _mid - 2;
+ else if ( data[p] > _JSON_value_trans_keys[_mid+1] )
+ _lower = _mid + 2;
+ else {
+ _trans += ((_mid - _keys)>>1);
+ break _match;
+ }
+ }
+ _trans += _klen;
+ }
+ } while (false);
+
+ cs = _JSON_value_trans_targs[_trans];
+
+ if ( _JSON_value_trans_actions[_trans] != 0 ) {
+ _acts = _JSON_value_trans_actions[_trans];
+ _nacts = (int) _JSON_value_actions[_acts++];
+ while ( _nacts-- > 0 )
+ {
+ switch ( _JSON_value_actions[_acts++] )
+ {
+ case 0:
+// line 331 "Parser.rl"
+ {
+ result = getRuntime().getNil();
+ }
+ break;
+ case 1:
+// line 334 "Parser.rl"
+ {
+ result = getRuntime().getFalse();
+ }
+ break;
+ case 2:
+// line 337 "Parser.rl"
+ {
+ result = getRuntime().getTrue();
+ }
+ break;
+ case 3:
+// line 340 "Parser.rl"
+ {
+ if (parser.allowNaN) {
+ result = getConstant(CONST_NAN);
+ } else {
+ throw unexpectedToken(p - 2, pe);
+ }
+ }
+ break;
+ case 4:
+// line 347 "Parser.rl"
+ {
+ if (parser.allowNaN) {
+ result = getConstant(CONST_INFINITY);
+ } else {
+ throw unexpectedToken(p - 7, pe);
+ }
+ }
+ break;
+ case 5:
+// line 354 "Parser.rl"
+ {
+ if (pe > p + 9 &&
+ absSubSequence(p, p + 9).toString().equals(JSON_MINUS_INFINITY)) {
+
+ if (parser.allowNaN) {
+ result = getConstant(CONST_MINUS_INFINITY);
+ {p = (( p + 10))-1;}
+ p--;
+ { p += 1; _goto_targ = 5; if (true) continue _goto;}
+ } else {
+ throw unexpectedToken(p, pe);
+ }
+ }
+ ParserResult res = parseFloat(p, pe);
+ if (res != null) {
+ result = res.result;
+ {p = (( res.p))-1;}
+ }
+ res = parseInteger(p, pe);
+ if (res != null) {
+ result = res.result;
+ {p = (( res.p))-1;}
+ }
+ p--;
+ { p += 1; _goto_targ = 5; if (true) continue _goto;}
+ }
+ break;
+ case 6:
+// line 380 "Parser.rl"
+ {
+ ParserResult res = parseString(p, pe);
+ if (res == null) {
+ p--;
+ { p += 1; _goto_targ = 5; if (true) continue _goto;}
+ } else {
+ result = res.result;
+ {p = (( res.p))-1;}
+ }
+ }
+ break;
+ case 7:
+// line 390 "Parser.rl"
+ {
+ currentNesting++;
+ ParserResult res = parseArray(p, pe);
+ currentNesting--;
+ if (res == null) {
+ p--;
+ { p += 1; _goto_targ = 5; if (true) continue _goto;}
+ } else {
+ result = res.result;
+ {p = (( res.p))-1;}
+ }
+ }
+ break;
+ case 8:
+// line 402 "Parser.rl"
+ {
+ currentNesting++;
+ ParserResult res = parseObject(p, pe);
+ currentNesting--;
+ if (res == null) {
+ p--;
+ { p += 1; _goto_targ = 5; if (true) continue _goto;}
+ } else {
+ result = res.result;
+ {p = (( res.p))-1;}
+ }
+ }
+ break;
+// line 638 "Parser.java"
+ }
+ }
+ }
+
+case 2:
+ if ( cs == 0 ) {
+ _goto_targ = 5;
+ continue _goto;
+ }
+ if ( ++p != pe ) {
+ _goto_targ = 1;
+ continue _goto;
+ }
+case 4:
+case 5:
+ }
+ break; }
+ }
+
+// line 437 "Parser.rl"
+
+ if (cs >= JSON_value_first_final && result != null) {
+ return new ParserResult(result, p);
+ } else {
+ return null;
+ }
+ }
+
+
+// line 668 "Parser.java"
+private static byte[] init__JSON_integer_actions_0()
+{
+ return new byte [] {
+ 0, 1, 0
+ };
+}
+
+private static final byte _JSON_integer_actions[] = init__JSON_integer_actions_0();
+
+
+private static byte[] init__JSON_integer_key_offsets_0()
+{
+ return new byte [] {
+ 0, 0, 4, 7, 9, 11
+ };
+}
+
+private static final byte _JSON_integer_key_offsets[] = init__JSON_integer_key_offsets_0();
+
+
+private static char[] init__JSON_integer_trans_keys_0()
+{
+ return new char [] {
+ 45, 48, 49, 57, 48, 49, 57, 48, 57, 48, 57, 0
+ };
+}
+
+private static final char _JSON_integer_trans_keys[] = init__JSON_integer_trans_keys_0();
+
+
+private static byte[] init__JSON_integer_single_lengths_0()
+{
+ return new byte [] {
+ 0, 2, 1, 0, 0, 0
+ };
+}
+
+private static final byte _JSON_integer_single_lengths[] = init__JSON_integer_single_lengths_0();
+
+
+private static byte[] init__JSON_integer_range_lengths_0()
+{
+ return new byte [] {
+ 0, 1, 1, 1, 1, 0
+ };
+}
+
+private static final byte _JSON_integer_range_lengths[] = init__JSON_integer_range_lengths_0();
+
+
+private static byte[] init__JSON_integer_index_offsets_0()
+{
+ return new byte [] {
+ 0, 0, 4, 7, 9, 11
+ };
+}
+
+private static final byte _JSON_integer_index_offsets[] = init__JSON_integer_index_offsets_0();
+
+
+private static byte[] init__JSON_integer_indicies_0()
+{
+ return new byte [] {
+ 0, 2, 3, 1, 2, 3, 1, 1, 4, 3, 4, 1,
+ 0
+ };
+}
+
+private static final byte _JSON_integer_indicies[] = init__JSON_integer_indicies_0();
+
+
+private static byte[] init__JSON_integer_trans_targs_0()
+{
+ return new byte [] {
+ 2, 0, 3, 4, 5
+ };
+}
+
+private static final byte _JSON_integer_trans_targs[] = init__JSON_integer_trans_targs_0();
+
+
+private static byte[] init__JSON_integer_trans_actions_0()
+{
+ return new byte [] {
+ 0, 0, 0, 0, 1
+ };
+}
+
+private static final byte _JSON_integer_trans_actions[] = init__JSON_integer_trans_actions_0();
+
+
+static final int JSON_integer_start = 1;
+static final int JSON_integer_first_final = 5;
+static final int JSON_integer_error = 0;
+
+static final int JSON_integer_en_main = 1;
+
+
+// line 456 "Parser.rl"
+
+
+ ParserResult parseInteger(int p, int pe) {
+ int cs = EVIL;
+
+
+// line 774 "Parser.java"
+ {
+ cs = JSON_integer_start;
+ }
+
+// line 462 "Parser.rl"
+ int memo = p;
+
+// line 782 "Parser.java"
+ {
+ int _klen;
+ int _trans = 0;
+ int _acts;
+ int _nacts;
+ int _keys;
+ int _goto_targ = 0;
+
+ _goto: while (true) {
+ switch ( _goto_targ ) {
+ case 0:
+ if ( p == pe ) {
+ _goto_targ = 4;
+ continue _goto;
+ }
+ if ( cs == 0 ) {
+ _goto_targ = 5;
+ continue _goto;
+ }
+case 1:
+ _match: do {
+ _keys = _JSON_integer_key_offsets[cs];
+ _trans = _JSON_integer_index_offsets[cs];
+ _klen = _JSON_integer_single_lengths[cs];
+ if ( _klen > 0 ) {
+ int _lower = _keys;
+ int _mid;
+ int _upper = _keys + _klen - 1;
+ while (true) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + ((_upper-_lower) >> 1);
+ if ( data[p] < _JSON_integer_trans_keys[_mid] )
+ _upper = _mid - 1;
+ else if ( data[p] > _JSON_integer_trans_keys[_mid] )
+ _lower = _mid + 1;
+ else {
+ _trans += (_mid - _keys);
+ break _match;
+ }
+ }
+ _keys += _klen;
+ _trans += _klen;
+ }
+
+ _klen = _JSON_integer_range_lengths[cs];
+ if ( _klen > 0 ) {
+ int _lower = _keys;
+ int _mid;
+ int _upper = _keys + (_klen<<1) - 2;
+ while (true) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + (((_upper-_lower) >> 1) & ~1);
+ if ( data[p] < _JSON_integer_trans_keys[_mid] )
+ _upper = _mid - 2;
+ else if ( data[p] > _JSON_integer_trans_keys[_mid+1] )
+ _lower = _mid + 2;
+ else {
+ _trans += ((_mid - _keys)>>1);
+ break _match;
+ }
+ }
+ _trans += _klen;
+ }
+ } while (false);
+
+ _trans = _JSON_integer_indicies[_trans];
+ cs = _JSON_integer_trans_targs[_trans];
+
+ if ( _JSON_integer_trans_actions[_trans] != 0 ) {
+ _acts = _JSON_integer_trans_actions[_trans];
+ _nacts = (int) _JSON_integer_actions[_acts++];
+ while ( _nacts-- > 0 )
+ {
+ switch ( _JSON_integer_actions[_acts++] )
+ {
+ case 0:
+// line 450 "Parser.rl"
+ {
+ p--;
+ { p += 1; _goto_targ = 5; if (true) continue _goto;}
+ }
+ break;
+// line 869 "Parser.java"
+ }
+ }
+ }
+
+case 2:
+ if ( cs == 0 ) {
+ _goto_targ = 5;
+ continue _goto;
+ }
+ if ( ++p != pe ) {
+ _goto_targ = 1;
+ continue _goto;
+ }
+case 4:
+case 5:
+ }
+ break; }
+ }
+
+// line 464 "Parser.rl"
+
+ if (cs < JSON_integer_first_final) {
+ return null;
+ }
+
+ ByteList num = absSubSequence(memo, p);
+ // note: this is actually a shared string, but since it is temporary and
+ // read-only, it doesn't really matter
+ RubyString expr = RubyString.newStringLight(getRuntime(), num);
+ RubyInteger number = RubyNumeric.str2inum(getRuntime(), expr, 10, true);
+ return new ParserResult(number, p + 1);
+ }
+
+
+// line 904 "Parser.java"
+private static byte[] init__JSON_float_actions_0()
+{
+ return new byte [] {
+ 0, 1, 0
+ };
+}
+
+private static final byte _JSON_float_actions[] = init__JSON_float_actions_0();
+
+
+private static byte[] init__JSON_float_key_offsets_0()
+{
+ return new byte [] {
+ 0, 0, 4, 7, 10, 12, 18, 22, 24, 30, 35
+ };
+}
+
+private static final byte _JSON_float_key_offsets[] = init__JSON_float_key_offsets_0();
+
+
+private static char[] init__JSON_float_trans_keys_0()
+{
+ return new char [] {
+ 45, 48, 49, 57, 48, 49, 57, 46, 69, 101, 48, 57,
+ 69, 101, 45, 46, 48, 57, 43, 45, 48, 57, 48, 57,
+ 69, 101, 45, 46, 48, 57, 46, 69, 101, 48, 57, 0
+ };
+}
+
+private static final char _JSON_float_trans_keys[] = init__JSON_float_trans_keys_0();
+
+
+private static byte[] init__JSON_float_single_lengths_0()
+{
+ return new byte [] {
+ 0, 2, 1, 3, 0, 2, 2, 0, 2, 3, 0
+ };
+}
+
+private static final byte _JSON_float_single_lengths[] = init__JSON_float_single_lengths_0();
+
+
+private static byte[] init__JSON_float_range_lengths_0()
+{
+ return new byte [] {
+ 0, 1, 1, 0, 1, 2, 1, 1, 2, 1, 0
+ };
+}
+
+private static final byte _JSON_float_range_lengths[] = init__JSON_float_range_lengths_0();
+
+
+private static byte[] init__JSON_float_index_offsets_0()
+{
+ return new byte [] {
+ 0, 0, 4, 7, 11, 13, 18, 22, 24, 29, 34
+ };
+}
+
+private static final byte _JSON_float_index_offsets[] = init__JSON_float_index_offsets_0();
+
+
+private static byte[] init__JSON_float_indicies_0()
+{
+ return new byte [] {
+ 0, 2, 3, 1, 2, 3, 1, 4, 5, 5, 1, 6,
+ 1, 5, 5, 1, 6, 7, 8, 8, 9, 1, 9, 1,
+ 1, 1, 1, 9, 7, 4, 5, 5, 3, 1, 1, 0
+ };
+}
+
+private static final byte _JSON_float_indicies[] = init__JSON_float_indicies_0();
+
+
+private static byte[] init__JSON_float_trans_targs_0()
+{
+ return new byte [] {
+ 2, 0, 3, 9, 4, 6, 5, 10, 7, 8
+ };
+}
+
+private static final byte _JSON_float_trans_targs[] = init__JSON_float_trans_targs_0();
+
+
+private static byte[] init__JSON_float_trans_actions_0()
+{
+ return new byte [] {
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0
+ };
+}
+
+private static final byte _JSON_float_trans_actions[] = init__JSON_float_trans_actions_0();
+
+
+static final int JSON_float_start = 1;
+static final int JSON_float_first_final = 10;
+static final int JSON_float_error = 0;
+
+static final int JSON_float_en_main = 1;
+
+
+// line 492 "Parser.rl"
+
+
+ ParserResult parseFloat(int p, int pe) {
+ int cs = EVIL;
+
+
+// line 1013 "Parser.java"
+ {
+ cs = JSON_float_start;
+ }
+
+// line 498 "Parser.rl"
+ int memo = p;
+
+// line 1021 "Parser.java"
+ {
+ int _klen;
+ int _trans = 0;
+ int _acts;
+ int _nacts;
+ int _keys;
+ int _goto_targ = 0;
+
+ _goto: while (true) {
+ switch ( _goto_targ ) {
+ case 0:
+ if ( p == pe ) {
+ _goto_targ = 4;
+ continue _goto;
+ }
+ if ( cs == 0 ) {
+ _goto_targ = 5;
+ continue _goto;
+ }
+case 1:
+ _match: do {
+ _keys = _JSON_float_key_offsets[cs];
+ _trans = _JSON_float_index_offsets[cs];
+ _klen = _JSON_float_single_lengths[cs];
+ if ( _klen > 0 ) {
+ int _lower = _keys;
+ int _mid;
+ int _upper = _keys + _klen - 1;
+ while (true) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + ((_upper-_lower) >> 1);
+ if ( data[p] < _JSON_float_trans_keys[_mid] )
+ _upper = _mid - 1;
+ else if ( data[p] > _JSON_float_trans_keys[_mid] )
+ _lower = _mid + 1;
+ else {
+ _trans += (_mid - _keys);
+ break _match;
+ }
+ }
+ _keys += _klen;
+ _trans += _klen;
+ }
+
+ _klen = _JSON_float_range_lengths[cs];
+ if ( _klen > 0 ) {
+ int _lower = _keys;
+ int _mid;
+ int _upper = _keys + (_klen<<1) - 2;
+ while (true) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + (((_upper-_lower) >> 1) & ~1);
+ if ( data[p] < _JSON_float_trans_keys[_mid] )
+ _upper = _mid - 2;
+ else if ( data[p] > _JSON_float_trans_keys[_mid+1] )
+ _lower = _mid + 2;
+ else {
+ _trans += ((_mid - _keys)>>1);
+ break _match;
+ }
+ }
+ _trans += _klen;
+ }
+ } while (false);
+
+ _trans = _JSON_float_indicies[_trans];
+ cs = _JSON_float_trans_targs[_trans];
+
+ if ( _JSON_float_trans_actions[_trans] != 0 ) {
+ _acts = _JSON_float_trans_actions[_trans];
+ _nacts = (int) _JSON_float_actions[_acts++];
+ while ( _nacts-- > 0 )
+ {
+ switch ( _JSON_float_actions[_acts++] )
+ {
+ case 0:
+// line 483 "Parser.rl"
+ {
+ p--;
+ { p += 1; _goto_targ = 5; if (true) continue _goto;}
+ }
+ break;
+// line 1108 "Parser.java"
+ }
+ }
+ }
+
+case 2:
+ if ( cs == 0 ) {
+ _goto_targ = 5;
+ continue _goto;
+ }
+ if ( ++p != pe ) {
+ _goto_targ = 1;
+ continue _goto;
+ }
+case 4:
+case 5:
+ }
+ break; }
+ }
+
+// line 500 "Parser.rl"
+
+ if (cs < JSON_float_first_final) {
+ return null;
+ }
+
+ ByteList num = absSubSequence(memo, p);
+ // note: this is actually a shared string, but since it is temporary and
+ // read-only, it doesn't really matter
+ RubyString expr = RubyString.newStringLight(getRuntime(), num);
+ RubyFloat number = RubyNumeric.str2fnum(getRuntime(), expr, true);
+ return new ParserResult(number, p + 1);
+ }
+
+
+// line 1143 "Parser.java"
+private static byte[] init__JSON_string_actions_0()
+{
+ return new byte [] {
+ 0, 2, 0, 1
+ };
+}
+
+private static final byte _JSON_string_actions[] = init__JSON_string_actions_0();
+
+
+private static byte[] init__JSON_string_key_offsets_0()
+{
+ return new byte [] {
+ 0, 0, 1, 5, 8, 14, 20, 26, 32
+ };
+}
+
+private static final byte _JSON_string_key_offsets[] = init__JSON_string_key_offsets_0();
+
+
+private static char[] init__JSON_string_trans_keys_0()
+{
+ return new char [] {
+ 34, 34, 92, 0, 31, 117, 0, 31, 48, 57, 65, 70,
+ 97, 102, 48, 57, 65, 70, 97, 102, 48, 57, 65, 70,
+ 97, 102, 48, 57, 65, 70, 97, 102, 0
+ };
+}
+
+private static final char _JSON_string_trans_keys[] = init__JSON_string_trans_keys_0();
+
+
+private static byte[] init__JSON_string_single_lengths_0()
+{
+ return new byte [] {
+ 0, 1, 2, 1, 0, 0, 0, 0, 0
+ };
+}
+
+private static final byte _JSON_string_single_lengths[] = init__JSON_string_single_lengths_0();
+
+
+private static byte[] init__JSON_string_range_lengths_0()
+{
+ return new byte [] {
+ 0, 0, 1, 1, 3, 3, 3, 3, 0
+ };
+}
+
+private static final byte _JSON_string_range_lengths[] = init__JSON_string_range_lengths_0();
+
+
+private static byte[] init__JSON_string_index_offsets_0()
+{
+ return new byte [] {
+ 0, 0, 2, 6, 9, 13, 17, 21, 25
+ };
+}
+
+private static final byte _JSON_string_index_offsets[] = init__JSON_string_index_offsets_0();
+
+
+private static byte[] init__JSON_string_indicies_0()
+{
+ return new byte [] {
+ 0, 1, 2, 3, 1, 0, 4, 1, 0, 5, 5, 5,
+ 1, 6, 6, 6, 1, 7, 7, 7, 1, 0, 0, 0,
+ 1, 1, 0
+ };
+}
+
+private static final byte _JSON_string_indicies[] = init__JSON_string_indicies_0();
+
+
+private static byte[] init__JSON_string_trans_targs_0()
+{
+ return new byte [] {
+ 2, 0, 8, 3, 4, 5, 6, 7
+ };
+}
+
+private static final byte _JSON_string_trans_targs[] = init__JSON_string_trans_targs_0();
+
+
+private static byte[] init__JSON_string_trans_actions_0()
+{
+ return new byte [] {
+ 0, 0, 1, 0, 0, 0, 0, 0
+ };
+}
+
+private static final byte _JSON_string_trans_actions[] = init__JSON_string_trans_actions_0();
+
+
+static final int JSON_string_start = 1;
+static final int JSON_string_first_final = 8;
+static final int JSON_string_error = 0;
+
+static final int JSON_string_en_main = 1;
+
+
+// line 544 "Parser.rl"
+
+
+ ParserResult parseString(int p, int pe) {
+ int cs = EVIL;
+ RubyString result = null;
+
+
+// line 1253 "Parser.java"
+ {
+ cs = JSON_string_start;
+ }
+
+// line 551 "Parser.rl"
+ int memo = p;
+
+// line 1261 "Parser.java"
+ {
+ int _klen;
+ int _trans = 0;
+ int _acts;
+ int _nacts;
+ int _keys;
+ int _goto_targ = 0;
+
+ _goto: while (true) {
+ switch ( _goto_targ ) {
+ case 0:
+ if ( p == pe ) {
+ _goto_targ = 4;
+ continue _goto;
+ }
+ if ( cs == 0 ) {
+ _goto_targ = 5;
+ continue _goto;
+ }
+case 1:
+ _match: do {
+ _keys = _JSON_string_key_offsets[cs];
+ _trans = _JSON_string_index_offsets[cs];
+ _klen = _JSON_string_single_lengths[cs];
+ if ( _klen > 0 ) {
+ int _lower = _keys;
+ int _mid;
+ int _upper = _keys + _klen - 1;
+ while (true) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + ((_upper-_lower) >> 1);
+ if ( data[p] < _JSON_string_trans_keys[_mid] )
+ _upper = _mid - 1;
+ else if ( data[p] > _JSON_string_trans_keys[_mid] )
+ _lower = _mid + 1;
+ else {
+ _trans += (_mid - _keys);
+ break _match;
+ }
+ }
+ _keys += _klen;
+ _trans += _klen;
+ }
+
+ _klen = _JSON_string_range_lengths[cs];
+ if ( _klen > 0 ) {
+ int _lower = _keys;
+ int _mid;
+ int _upper = _keys + (_klen<<1) - 2;
+ while (true) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + (((_upper-_lower) >> 1) & ~1);
+ if ( data[p] < _JSON_string_trans_keys[_mid] )
+ _upper = _mid - 2;
+ else if ( data[p] > _JSON_string_trans_keys[_mid+1] )
+ _lower = _mid + 2;
+ else {
+ _trans += ((_mid - _keys)>>1);
+ break _match;
+ }
+ }
+ _trans += _klen;
+ }
+ } while (false);
+
+ _trans = _JSON_string_indicies[_trans];
+ cs = _JSON_string_trans_targs[_trans];
+
+ if ( _JSON_string_trans_actions[_trans] != 0 ) {
+ _acts = _JSON_string_trans_actions[_trans];
+ _nacts = (int) _JSON_string_actions[_acts++];
+ while ( _nacts-- > 0 )
+ {
+ switch ( _JSON_string_actions[_acts++] )
+ {
+ case 0:
+// line 519 "Parser.rl"
+ {
+ int offset = byteList.begin();
+ ByteList decoded = decoder.decode(byteList, memo + 1 - offset,
+ p - offset);
+ result = getRuntime().newString(decoded);
+ if (result == null) {
+ p--;
+ { p += 1; _goto_targ = 5; if (true) continue _goto;}
+ } else {
+ {p = (( p + 1))-1;}
+ }
+ }
+ break;
+ case 1:
+// line 532 "Parser.rl"
+ {
+ p--;
+ { p += 1; _goto_targ = 5; if (true) continue _goto;}
+ }
+ break;
+// line 1363 "Parser.java"
+ }
+ }
+ }
+
+case 2:
+ if ( cs == 0 ) {
+ _goto_targ = 5;
+ continue _goto;
+ }
+ if ( ++p != pe ) {
+ _goto_targ = 1;
+ continue _goto;
+ }
+case 4:
+case 5:
+ }
+ break; }
+ }
+
+// line 553 "Parser.rl"
+
+ if (cs >= JSON_string_first_final && result != null) {
+ return new ParserResult(result, p + 1);
+ } else {
+ return null;
+ }
+ }
+
+
+// line 1393 "Parser.java"
+private static byte[] init__JSON_array_actions_0()
+{
+ return new byte [] {
+ 0, 1, 0, 1, 1
+ };
+}
+
+private static final byte _JSON_array_actions[] = init__JSON_array_actions_0();
+
+
+private static byte[] init__JSON_array_key_offsets_0()
+{
+ return new byte [] {
+ 0, 0, 1, 18, 25, 41, 43, 44, 46, 47, 49, 50,
+ 52, 53, 55, 56, 58, 59
+ };
+}
+
+private static final byte _JSON_array_key_offsets[] = init__JSON_array_key_offsets_0();
+
+
+private static char[] init__JSON_array_trans_keys_0()
+{
+ return new char [] {
+ 91, 13, 32, 34, 45, 47, 73, 78, 91, 93, 102, 110,
+ 116, 123, 9, 10, 48, 57, 13, 32, 44, 47, 93, 9,
+ 10, 13, 32, 34, 45, 47, 73, 78, 91, 102, 110, 116,
+ 123, 9, 10, 48, 57, 42, 47, 42, 42, 47, 10, 42,
+ 47, 42, 42, 47, 10, 42, 47, 42, 42, 47, 10, 0
+ };
+}
+
+private static final char _JSON_array_trans_keys[] = init__JSON_array_trans_keys_0();
+
+
+private static byte[] init__JSON_array_single_lengths_0()
+{
+ return new byte [] {
+ 0, 1, 13, 5, 12, 2, 1, 2, 1, 2, 1, 2,
+ 1, 2, 1, 2, 1, 0
+ };
+}
+
+private static final byte _JSON_array_single_lengths[] = init__JSON_array_single_lengths_0();
+
+
+private static byte[] init__JSON_array_range_lengths_0()
+{
+ return new byte [] {
+ 0, 0, 2, 1, 2, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0
+ };
+}
+
+private static final byte _JSON_array_range_lengths[] = init__JSON_array_range_lengths_0();
+
+
+private static byte[] init__JSON_array_index_offsets_0()
+{
+ return new byte [] {
+ 0, 0, 2, 18, 25, 40, 43, 45, 48, 50, 53, 55,
+ 58, 60, 63, 65, 68, 70
+ };
+}
+
+private static final byte _JSON_array_index_offsets[] = init__JSON_array_index_offsets_0();
+
+
+private static byte[] init__JSON_array_indicies_0()
+{
+ return new byte [] {
+ 0, 1, 0, 0, 2, 2, 3, 2, 2, 2, 4, 2,
+ 2, 2, 2, 0, 2, 1, 5, 5, 6, 7, 4, 5,
+ 1, 6, 6, 2, 2, 8, 2, 2, 2, 2, 2, 2,
+ 2, 6, 2, 1, 9, 10, 1, 11, 9, 11, 6, 9,
+ 6, 10, 12, 13, 1, 14, 12, 14, 5, 12, 5, 13,
+ 15, 16, 1, 17, 15, 17, 0, 15, 0, 16, 1, 0
+ };
+}
+
+private static final byte _JSON_array_indicies[] = init__JSON_array_indicies_0();
+
+
+private static byte[] init__JSON_array_trans_targs_0()
+{
+ return new byte [] {
+ 2, 0, 3, 13, 17, 3, 4, 9, 5, 6, 8, 7,
+ 10, 12, 11, 14, 16, 15
+ };
+}
+
+private static final byte _JSON_array_trans_targs[] = init__JSON_array_trans_targs_0();
+
+
+private static byte[] init__JSON_array_trans_actions_0()
+{
+ return new byte [] {
+ 0, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0
+ };
+}
+
+private static final byte _JSON_array_trans_actions[] = init__JSON_array_trans_actions_0();
+
+
+static final int JSON_array_start = 1;
+static final int JSON_array_first_final = 17;
+static final int JSON_array_error = 0;
+
+static final int JSON_array_en_main = 1;
+
+
+// line 594 "Parser.rl"
+
+
+ ParserResult parseArray(int p, int pe) {
+ int cs = EVIL;
+
+ if (parser.maxNesting > 0 && currentNesting > parser.maxNesting) {
+ throw newException(Utils.M_NESTING_ERROR,
+ "nesting of " + currentNesting + " is too deep");
+ }
+
+ // this is guaranteed to be a RubyArray due to the earlier
+ // allocator test at OptionsReader#getClass
+ RubyArray result =
+ (RubyArray)parser.arrayClass.newInstance(context,
+ IRubyObject.NULL_ARRAY, Block.NULL_BLOCK);
+
+
+// line 1524 "Parser.java"
+ {
+ cs = JSON_array_start;
+ }
+
+// line 611 "Parser.rl"
+
+// line 1531 "Parser.java"
+ {
+ int _klen;
+ int _trans = 0;
+ int _acts;
+ int _nacts;
+ int _keys;
+ int _goto_targ = 0;
+
+ _goto: while (true) {
+ switch ( _goto_targ ) {
+ case 0:
+ if ( p == pe ) {
+ _goto_targ = 4;
+ continue _goto;
+ }
+ if ( cs == 0 ) {
+ _goto_targ = 5;
+ continue _goto;
+ }
+case 1:
+ _match: do {
+ _keys = _JSON_array_key_offsets[cs];
+ _trans = _JSON_array_index_offsets[cs];
+ _klen = _JSON_array_single_lengths[cs];
+ if ( _klen > 0 ) {
+ int _lower = _keys;
+ int _mid;
+ int _upper = _keys + _klen - 1;
+ while (true) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + ((_upper-_lower) >> 1);
+ if ( data[p] < _JSON_array_trans_keys[_mid] )
+ _upper = _mid - 1;
+ else if ( data[p] > _JSON_array_trans_keys[_mid] )
+ _lower = _mid + 1;
+ else {
+ _trans += (_mid - _keys);
+ break _match;
+ }
+ }
+ _keys += _klen;
+ _trans += _klen;
+ }
+
+ _klen = _JSON_array_range_lengths[cs];
+ if ( _klen > 0 ) {
+ int _lower = _keys;
+ int _mid;
+ int _upper = _keys + (_klen<<1) - 2;
+ while (true) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + (((_upper-_lower) >> 1) & ~1);
+ if ( data[p] < _JSON_array_trans_keys[_mid] )
+ _upper = _mid - 2;
+ else if ( data[p] > _JSON_array_trans_keys[_mid+1] )
+ _lower = _mid + 2;
+ else {
+ _trans += ((_mid - _keys)>>1);
+ break _match;
+ }
+ }
+ _trans += _klen;
+ }
+ } while (false);
+
+ _trans = _JSON_array_indicies[_trans];
+ cs = _JSON_array_trans_targs[_trans];
+
+ if ( _JSON_array_trans_actions[_trans] != 0 ) {
+ _acts = _JSON_array_trans_actions[_trans];
+ _nacts = (int) _JSON_array_actions[_acts++];
+ while ( _nacts-- > 0 )
+ {
+ switch ( _JSON_array_actions[_acts++] )
+ {
+ case 0:
+// line 567 "Parser.rl"
+ {
+ ParserResult res = parseValue(p, pe);
+ if (res == null) {
+ p--;
+ { p += 1; _goto_targ = 5; if (true) continue _goto;}
+ } else {
+ result.append(res.result);
+ {p = (( res.p))-1;}
+ }
+ }
+ break;
+ case 1:
+// line 578 "Parser.rl"
+ {
+ p--;
+ { p += 1; _goto_targ = 5; if (true) continue _goto;}
+ }
+ break;
+// line 1631 "Parser.java"
+ }
+ }
+ }
+
+case 2:
+ if ( cs == 0 ) {
+ _goto_targ = 5;
+ continue _goto;
+ }
+ if ( ++p != pe ) {
+ _goto_targ = 1;
+ continue _goto;
+ }
+case 4:
+case 5:
+ }
+ break; }
+ }
+
+// line 612 "Parser.rl"
+
+ if (cs >= JSON_array_first_final) {
+ return new ParserResult(result, p + 1);
+ } else {
+ throw unexpectedToken(p, pe);
+ }
+ }
+
+
+// line 1661 "Parser.java"
+private static byte[] init__JSON_object_actions_0()
+{
+ return new byte [] {
+ 0, 1, 0, 1, 1, 1, 2
+ };
+}
+
+private static final byte _JSON_object_actions[] = init__JSON_object_actions_0();
+
+
+private static byte[] init__JSON_object_key_offsets_0()
+{
+ return new byte [] {
+ 0, 0, 1, 8, 14, 16, 17, 19, 20, 36, 43, 49,
+ 51, 52, 54, 55, 57, 58, 60, 61, 63, 64, 66, 67,
+ 69, 70, 72, 73
+ };
+}
+
+private static final byte _JSON_object_key_offsets[] = init__JSON_object_key_offsets_0();
+
+
+private static char[] init__JSON_object_trans_keys_0()
+{
+ return new char [] {
+ 123, 13, 32, 34, 47, 125, 9, 10, 13, 32, 47, 58,
+ 9, 10, 42, 47, 42, 42, 47, 10, 13, 32, 34, 45,
+ 47, 73, 78, 91, 102, 110, 116, 123, 9, 10, 48, 57,
+ 13, 32, 44, 47, 125, 9, 10, 13, 32, 34, 47, 9,
+ 10, 42, 47, 42, 42, 47, 10, 42, 47, 42, 42, 47,
+ 10, 42, 47, 42, 42, 47, 10, 42, 47, 42, 42, 47,
+ 10, 0
+ };
+}
+
+private static final char _JSON_object_trans_keys[] = init__JSON_object_trans_keys_0();
+
+
+private static byte[] init__JSON_object_single_lengths_0()
+{
+ return new byte [] {
+ 0, 1, 5, 4, 2, 1, 2, 1, 12, 5, 4, 2,
+ 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2,
+ 1, 2, 1, 0
+ };
+}
+
+private static final byte _JSON_object_single_lengths[] = init__JSON_object_single_lengths_0();
+
+
+private static byte[] init__JSON_object_range_lengths_0()
+{
+ return new byte [] {
+ 0, 0, 1, 1, 0, 0, 0, 0, 2, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0
+ };
+}
+
+private static final byte _JSON_object_range_lengths[] = init__JSON_object_range_lengths_0();
+
+
+private static byte[] init__JSON_object_index_offsets_0()
+{
+ return new byte [] {
+ 0, 0, 2, 9, 15, 18, 20, 23, 25, 40, 47, 53,
+ 56, 58, 61, 63, 66, 68, 71, 73, 76, 78, 81, 83,
+ 86, 88, 91, 93
+ };
+}
+
+private static final byte _JSON_object_index_offsets[] = init__JSON_object_index_offsets_0();
+
+
+private static byte[] init__JSON_object_indicies_0()
+{
+ return new byte [] {
+ 0, 1, 0, 0, 2, 3, 4, 0, 1, 5, 5, 6,
+ 7, 5, 1, 8, 9, 1, 10, 8, 10, 5, 8, 5,
+ 9, 7, 7, 11, 11, 12, 11, 11, 11, 11, 11, 11,
+ 11, 7, 11, 1, 13, 13, 14, 15, 4, 13, 1, 14,
+ 14, 2, 16, 14, 1, 17, 18, 1, 19, 17, 19, 14,
+ 17, 14, 18, 20, 21, 1, 22, 20, 22, 13, 20, 13,
+ 21, 23, 24, 1, 25, 23, 25, 7, 23, 7, 24, 26,
+ 27, 1, 28, 26, 28, 0, 26, 0, 27, 1, 0
+ };
+}
+
+private static final byte _JSON_object_indicies[] = init__JSON_object_indicies_0();
+
+
+private static byte[] init__JSON_object_trans_targs_0()
+{
+ return new byte [] {
+ 2, 0, 3, 23, 27, 3, 4, 8, 5, 7, 6, 9,
+ 19, 9, 10, 15, 11, 12, 14, 13, 16, 18, 17, 20,
+ 22, 21, 24, 26, 25
+ };
+}
+
+private static final byte _JSON_object_trans_targs[] = init__JSON_object_trans_targs_0();
+
+
+private static byte[] init__JSON_object_trans_actions_0()
+{
+ return new byte [] {
+ 0, 0, 3, 0, 5, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0
+ };
+}
+
+private static final byte _JSON_object_trans_actions[] = init__JSON_object_trans_actions_0();
+
+
+static final int JSON_object_start = 1;
+static final int JSON_object_first_final = 27;
+static final int JSON_object_error = 0;
+
+static final int JSON_object_en_main = 1;
+
+
+// line 668 "Parser.rl"
+
+
+ ParserResult parseObject(int p, int pe) {
+ int cs = EVIL;
+ IRubyObject lastName = null;
+
+ if (parser.maxNesting > 0 && currentNesting > parser.maxNesting) {
+ throw newException(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
+ RubyHash result =
+ (RubyHash)parser.objectClass.newInstance(context,
+ IRubyObject.NULL_ARRAY, Block.NULL_BLOCK);
+
+
+// line 1803 "Parser.java"
+ {
+ cs = JSON_object_start;
+ }
+
+// line 686 "Parser.rl"
+
+// line 1810 "Parser.java"
+ {
+ int _klen;
+ int _trans = 0;
+ int _acts;
+ int _nacts;
+ int _keys;
+ int _goto_targ = 0;
+
+ _goto: while (true) {
+ switch ( _goto_targ ) {
+ case 0:
+ if ( p == pe ) {
+ _goto_targ = 4;
+ continue _goto;
+ }
+ if ( cs == 0 ) {
+ _goto_targ = 5;
+ continue _goto;
+ }
+case 1:
+ _match: do {
+ _keys = _JSON_object_key_offsets[cs];
+ _trans = _JSON_object_index_offsets[cs];
+ _klen = _JSON_object_single_lengths[cs];
+ if ( _klen > 0 ) {
+ int _lower = _keys;
+ int _mid;
+ int _upper = _keys + _klen - 1;
+ while (true) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + ((_upper-_lower) >> 1);
+ if ( data[p] < _JSON_object_trans_keys[_mid] )
+ _upper = _mid - 1;
+ else if ( data[p] > _JSON_object_trans_keys[_mid] )
+ _lower = _mid + 1;
+ else {
+ _trans += (_mid - _keys);
+ break _match;
+ }
+ }
+ _keys += _klen;
+ _trans += _klen;
+ }
+
+ _klen = _JSON_object_range_lengths[cs];
+ if ( _klen > 0 ) {
+ int _lower = _keys;
+ int _mid;
+ int _upper = _keys + (_klen<<1) - 2;
+ while (true) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + (((_upper-_lower) >> 1) & ~1);
+ if ( data[p] < _JSON_object_trans_keys[_mid] )
+ _upper = _mid - 2;
+ else if ( data[p] > _JSON_object_trans_keys[_mid+1] )
+ _lower = _mid + 2;
+ else {
+ _trans += ((_mid - _keys)>>1);
+ break _match;
+ }
+ }
+ _trans += _klen;
+ }
+ } while (false);
+
+ _trans = _JSON_object_indicies[_trans];
+ cs = _JSON_object_trans_targs[_trans];
+
+ if ( _JSON_object_trans_actions[_trans] != 0 ) {
+ _acts = _JSON_object_trans_actions[_trans];
+ _nacts = (int) _JSON_object_actions[_acts++];
+ while ( _nacts-- > 0 )
+ {
+ switch ( _JSON_object_actions[_acts++] )
+ {
+ case 0:
+// line 626 "Parser.rl"
+ {
+ ParserResult res = parseValue(p, pe);
+ if (res == null) {
+ p--;
+ { p += 1; _goto_targ = 5; if (true) continue _goto;}
+ } else {
+ result.op_aset(context, lastName, res.result);
+ {p = (( res.p))-1;}
+ }
+ }
+ break;
+ case 1:
+// line 637 "Parser.rl"
+ {
+ ParserResult res = parseString(p, pe);
+ if (res == null) {
+ p--;
+ { p += 1; _goto_targ = 5; if (true) continue _goto;}
+ } else {
+ RubyString name = (RubyString)res.result;
+ if (parser.symbolizeNames) {
+ lastName = context.getRuntime().is1_9()
+ ? name.intern19()
+ : name.intern();
+ } else {
+ lastName = name;
+ }
+ {p = (( res.p))-1;}
+ }
+ }
+ break;
+ case 2:
+// line 655 "Parser.rl"
+ {
+ p--;
+ { p += 1; _goto_targ = 5; if (true) continue _goto;}
+ }
+ break;
+// line 1930 "Parser.java"
+ }
+ }
+ }
+
+case 2:
+ if ( cs == 0 ) {
+ _goto_targ = 5;
+ continue _goto;
+ }
+ if ( ++p != pe ) {
+ _goto_targ = 1;
+ continue _goto;
+ }
+case 4:
+case 5:
+ }
+ break; }
+ }
+
+// line 687 "Parser.rl"
+
+ if (cs < JSON_object_first_final) {
+ return null;
+ }
+
+ IRubyObject returnedResult = result;
+
+ // attempt to de-serialize object
+ if (parser.createId != null) {
+ IRubyObject vKlassName = result.op_aref(context, parser.createId);
+ if (!vKlassName.isNil()) {
+ // might throw ArgumentError, we let it propagate
+ IRubyObject klass = parser.info.jsonModule.
+ callMethod(context, "deep_const_get", vKlassName);
+ if (klass.respondsTo("json_creatable?") &&
+ klass.callMethod(context, "json_creatable?").isTrue()) {
+
+ returnedResult = klass.callMethod(context, "json_create", result);
+ }
+ }
+ }
+ return new ParserResult(returnedResult, p + 1);
+ }
+
+
+// line 1976 "Parser.java"
+private static byte[] init__JSON_actions_0()
+{
+ return new byte [] {
+ 0, 1, 0, 1, 1
+ };
+}
+
+private static final byte _JSON_actions[] = init__JSON_actions_0();
+
+
+private static byte[] init__JSON_key_offsets_0()
+{
+ return new byte [] {
+ 0, 0, 7, 9, 10, 12, 13, 15, 16, 18, 19
+ };
+}
+
+private static final byte _JSON_key_offsets[] = init__JSON_key_offsets_0();
+
+
+private static char[] init__JSON_trans_keys_0()
+{
+ return new char [] {
+ 13, 32, 47, 91, 123, 9, 10, 42, 47, 42, 42, 47,
+ 10, 42, 47, 42, 42, 47, 10, 13, 32, 47, 9, 10,
+ 0
+ };
+}
+
+private static final char _JSON_trans_keys[] = init__JSON_trans_keys_0();
+
+
+private static byte[] init__JSON_single_lengths_0()
+{
+ return new byte [] {
+ 0, 5, 2, 1, 2, 1, 2, 1, 2, 1, 3
+ };
+}
+
+private static final byte _JSON_single_lengths[] = init__JSON_single_lengths_0();
+
+
+private static byte[] init__JSON_range_lengths_0()
+{
+ return new byte [] {
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1
+ };
+}
+
+private static final byte _JSON_range_lengths[] = init__JSON_range_lengths_0();
+
+
+private static byte[] init__JSON_index_offsets_0()
+{
+ return new byte [] {
+ 0, 0, 7, 10, 12, 15, 17, 20, 22, 25, 27
+ };
+}
+
+private static final byte _JSON_index_offsets[] = init__JSON_index_offsets_0();
+
+
+private static byte[] init__JSON_indicies_0()
+{
+ return new byte [] {
+ 0, 0, 2, 3, 4, 0, 1, 5, 6, 1, 7, 5,
+ 7, 0, 5, 0, 6, 8, 9, 1, 10, 8, 10, 11,
+ 8, 11, 9, 11, 11, 12, 11, 1, 0
+ };
+}
+
+private static final byte _JSON_indicies[] = init__JSON_indicies_0();
+
+
+private static byte[] init__JSON_trans_targs_0()
+{
+ return new byte [] {
+ 1, 0, 2, 10, 10, 3, 5, 4, 7, 9, 8, 10,
+ 6
+ };
+}
+
+private static final byte _JSON_trans_targs[] = init__JSON_trans_targs_0();
+
+
+private static byte[] init__JSON_trans_actions_0()
+{
+ return new byte [] {
+ 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0
+ };
+}
+
+private static final byte _JSON_trans_actions[] = init__JSON_trans_actions_0();
+
+
+static final int JSON_start = 1;
+static final int JSON_first_final = 10;
+static final int JSON_error = 0;
+
+static final int JSON_en_main = 1;
+
+
+// line 745 "Parser.rl"
+
+
+ public IRubyObject parse() {
+ int cs = EVIL;
+ int p, pe;
+ IRubyObject result = null;
+
+
+// line 2089 "Parser.java"
+ {
+ cs = JSON_start;
+ }
+
+// line 753 "Parser.rl"
+ p = byteList.begin();
+ pe = p + byteList.length();
+
+// line 2098 "Parser.java"
+ {
+ int _klen;
+ int _trans = 0;
+ int _acts;
+ int _nacts;
+ int _keys;
+ int _goto_targ = 0;
+
+ _goto: while (true) {
+ switch ( _goto_targ ) {
+ case 0:
+ if ( p == pe ) {
+ _goto_targ = 4;
+ continue _goto;
+ }
+ if ( cs == 0 ) {
+ _goto_targ = 5;
+ continue _goto;
+ }
+case 1:
+ _match: do {
+ _keys = _JSON_key_offsets[cs];
+ _trans = _JSON_index_offsets[cs];
+ _klen = _JSON_single_lengths[cs];
+ if ( _klen > 0 ) {
+ int _lower = _keys;
+ int _mid;
+ int _upper = _keys + _klen - 1;
+ while (true) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + ((_upper-_lower) >> 1);
+ if ( data[p] < _JSON_trans_keys[_mid] )
+ _upper = _mid - 1;
+ else if ( data[p] > _JSON_trans_keys[_mid] )
+ _lower = _mid + 1;
+ else {
+ _trans += (_mid - _keys);
+ break _match;
+ }
+ }
+ _keys += _klen;
+ _trans += _klen;
+ }
+
+ _klen = _JSON_range_lengths[cs];
+ if ( _klen > 0 ) {
+ int _lower = _keys;
+ int _mid;
+ int _upper = _keys + (_klen<<1) - 2;
+ while (true) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + (((_upper-_lower) >> 1) & ~1);
+ if ( data[p] < _JSON_trans_keys[_mid] )
+ _upper = _mid - 2;
+ else if ( data[p] > _JSON_trans_keys[_mid+1] )
+ _lower = _mid + 2;
+ else {
+ _trans += ((_mid - _keys)>>1);
+ break _match;
+ }
+ }
+ _trans += _klen;
+ }
+ } while (false);
+
+ _trans = _JSON_indicies[_trans];
+ cs = _JSON_trans_targs[_trans];
+
+ if ( _JSON_trans_actions[_trans] != 0 ) {
+ _acts = _JSON_trans_actions[_trans];
+ _nacts = (int) _JSON_actions[_acts++];
+ while ( _nacts-- > 0 )
+ {
+ switch ( _JSON_actions[_acts++] )
+ {
+ case 0:
+// line 717 "Parser.rl"
+ {
+ currentNesting = 1;
+ ParserResult res = parseObject(p, pe);
+ if (res == null) {
+ p--;
+ { p += 1; _goto_targ = 5; if (true) continue _goto;}
+ } else {
+ result = res.result;
+ {p = (( res.p))-1;}
+ }
+ }
+ break;
+ case 1:
+// line 729 "Parser.rl"
+ {
+ currentNesting = 1;
+ ParserResult res = parseArray(p, pe);
+ if (res == null) {
+ p--;
+ { p += 1; _goto_targ = 5; if (true) continue _goto;}
+ } else {
+ result = res.result;
+ {p = (( res.p))-1;}
+ }
+ }
+ break;
+// line 2206 "Parser.java"
+ }
+ }
+ }
+
+case 2:
+ if ( cs == 0 ) {
+ _goto_targ = 5;
+ continue _goto;
+ }
+ if ( ++p != pe ) {
+ _goto_targ = 1;
+ continue _goto;
+ }
+case 4:
+case 5:
+ }
+ break; }
+ }
+
+// line 756 "Parser.rl"
+
+ if (cs >= JSON_first_final && p == pe) {
+ return result;
+ } else {
+ throw unexpectedToken(p, pe);
+ }
+ }
+
+ /**
+ * Returns a subsequence of the source ByteList, based on source
+ * array byte offsets (i.e., the ByteList's own begin offset is not
+ * automatically added).
+ * @param start
+ * @param end
+ */
+ private ByteList absSubSequence(int absStart, int absEnd) {
+ int offset = byteList.begin();
+ return (ByteList)byteList.subSequence(absStart - offset,
+ absEnd - offset);
+ }
+
+ /**
+ * Retrieves a constant directly descended from the <code>JSON</code> module.
+ * @param name The constant name
+ */
+ private IRubyObject getConstant(String name) {
+ return parser.info.jsonModule.getConstant(name);
+ }
+
+ private RaiseException newException(String className, String message) {
+ return Utils.newException(context, className, message);
+ }
+
+ private RaiseException newException(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));
+ }
+ }
+}
diff --git a/java/src/json/ext/Parser.rl b/java/src/json/ext/Parser.rl
new file mode 100644
index 0000000..00badc8
--- /dev/null
+++ b/java/src/json/ext/Parser.rl
@@ -0,0 +1,799 @@
+/*
+ * This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
+ *
+ * Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
+ * for details.
+ */
+package json.ext;
+
+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;
+import org.jruby.RubyModule;
+import org.jruby.RubyNumeric;
+import org.jruby.RubyObject;
+import org.jruby.RubyString;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.exceptions.RaiseException;
+import org.jruby.runtime.Block;
+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;
+
+/**
+ * The <code>JSON::Ext::Parser</code> class.
+ *
+ * <p>This is the JSON parser implemented as a Java class. To use it as the
+ * standard parser, set
+ * <pre>JSON.parser = JSON::Ext::Parser</pre>
+ * This is performed for you when you <code>include "json/ext"</code>.
+ *
+ * <p>This class does not perform the actual parsing, just acts as an interface
+ * to Ruby code. When the {@link #parse()} method is invoked, a
+ * Parser.ParserSession object is instantiated, which handles the process.
+ *
+ * @author mernen
+ */
+public class Parser extends RubyObject {
+ private final RuntimeInfo info;
+ private RubyString vSource;
+ private RubyString createId;
+ private int maxNesting;
+ private boolean allowNaN;
+ private boolean symbolizeNames;
+ private RubyClass objectClass;
+ private RubyClass arrayClass;
+
+ private static final int DEFAULT_MAX_NESTING = 19;
+
+ private static final String JSON_MINUS_INFINITY = "-Infinity";
+ // constant names in the JSON module containing those values
+ private static final String CONST_NAN = "NaN";
+ 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);
+ }
+ };
+
+ /**
+ * Multiple-value return for internal parser methods.
+ *
+ * <p>All the <code>parse<var>Stuff</var></code> methods return instances of
+ * <code>ParserResult</code> when successful, or <code>null</code> when
+ * there's a problem with the input data.
+ */
+ static final class ParserResult {
+ /**
+ * The result of the successful parsing. Should never be
+ * <code>null</code>.
+ */
+ final IRubyObject result;
+ /**
+ * The point where the parser returned.
+ */
+ final int p;
+
+ ParserResult(IRubyObject result, int p) {
+ this.result = result;
+ this.p = p;
+ }
+ }
+
+ public Parser(Ruby runtime, RubyClass metaClass) {
+ super(runtime, metaClass);
+ info = RuntimeInfo.forRuntime(runtime);
+ }
+
+ /**
+ * <code>Parser.new(source, opts = {})</code>
+ *
+ * <p>Creates a new <code>JSON::Ext::Parser</code> instance for the string
+ * <code>source</code>.
+ * It will be configured by the <code>opts</code> Hash.
+ * <code>opts</code> can have the following keys:
+ *
+ * <dl>
+ * <dt><code>:max_nesting</code>
+ * <dd>The maximum depth of nesting allowed in the parsed data
+ * structures. Disable depth checking with <code>:max_nesting => false|nil|0</code>,
+ * it defaults to 19.
+ *
+ * <dt><code>:allow_nan</code>
+ * <dd>If set to <code>true</code>, allow <code>NaN</code>,
+ * <code>Infinity</code> and <code>-Infinity</code> in defiance of RFC 4627
+ * to be parsed by the Parser. This option defaults to <code>false</code>.
+ *
+ * <dt><code>:symbolize_names</code>
+ * <dd>If set to <code>true</code>, returns symbols for the names (keys) in
+ * a JSON object. Otherwise strings are returned, which is also the default.
+ *
+ * <dt><code>:create_additions</code>
+ * <dd>If set to <code>false</code>, the Parser doesn't create additions
+ * even if a matchin class and <code>create_id</code> was found. This option
+ * defaults to <code>true</code>.
+ *
+ * <dt><code>:object_class</code>
+ * <dd>Defaults to Hash.
+ *
+ * <dt><code>:array_class</code>
+ * <dd>Defaults to Array.
+ * </dl>
+ */
+ @JRubyMethod(name = "new", required = 1, optional = 1, meta = true)
+ public static IRubyObject newInstance(IRubyObject clazz, IRubyObject[] args, Block block) {
+ Parser parser = (Parser)((RubyClass)clazz).allocate();
+
+ parser.callInit(args, block);
+
+ return parser;
+ }
+
+ @JRubyMethod(required = 1, optional = 1, visibility = Visibility.PRIVATE)
+ public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
+ Ruby runtime = context.getRuntime();
+ RubyString source = convertEncoding(context, args[0].convertToString());
+
+ OptionsReader opts =
+ new OptionsReader(context, args.length > 1 ? args[1] : null);
+
+ this.maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING);
+ this.allowNaN = opts.getBool("allow_nan", false);
+ this.symbolizeNames = opts.getBool("symbolize_names", false);
+ this.createId =
+ opts.getBool("create_additions", true) ? getCreateId(context)
+ : null;
+ this.objectClass = opts.getClass("object_class", runtime.getHash());
+ this.arrayClass = opts.getClass("array_class", runtime.getArray());
+
+ this.vSource = source;
+ return this;
+ }
+
+ /**
+ * Checks the given string's encoding. If a non-UTF-8 encoding is detected,
+ * a converted copy is returned.
+ * Returns the source string if no conversion is needed.
+ */
+ private RubyString convertEncoding(ThreadContext context, RubyString source) {
+ ByteList bl = source.getByteList();
+ int len = bl.length();
+ if (len < 2) {
+ throw Utils.newException(context, Utils.M_PARSER_ERROR,
+ "A JSON text must at least contain two octets!");
+ }
+
+ if (info.encodingsSupported()) {
+ RubyEncoding encoding = (RubyEncoding)source.encoding(context);
+ if (encoding != info.ascii8bit) {
+ return (RubyString)source.encode(context, info.utf8);
+ }
+
+ String sniffedEncoding = sniffByteList(bl);
+ if (sniffedEncoding == null) return source; // assume UTF-8
+ return reinterpretEncoding(context, source, sniffedEncoding);
+ }
+
+ String sniffedEncoding = sniffByteList(bl);
+ if (sniffedEncoding == null) return source; // assume UTF-8
+ Ruby runtime = context.getRuntime();
+ return (RubyString)info.jsonModule.
+ callMethod(context, "iconv",
+ new IRubyObject[] {
+ runtime.newString("utf-8"),
+ runtime.newString(sniffedEncoding),
+ 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;
+ RubyString dup = (RubyString)str.dup();
+ dup.force_encoding(context, actualEncoding);
+ return (RubyString)dup.encode_bang(context, targetEncoding);
+ }
+
+ /**
+ * <code>Parser#parse()</code>
+ *
+ * <p>Parses the current JSON text <code>source</code> and returns the
+ * complete data structure as a result.
+ */
+ @JRubyMethod
+ public IRubyObject parse(ThreadContext context) {
+ return new ParserSession(this, context).parse();
+ }
+
+ /**
+ * <code>Parser#source()</code>
+ *
+ * <p>Returns a copy of the current <code>source</code> string, that was
+ * used to construct this Parser.
+ */
+ @JRubyMethod(name = "source")
+ public IRubyObject source_get() {
+ return vSource.dup();
+ }
+
+ /**
+ * Queries <code>JSON.create_id</code>. Returns <code>null</code> if it is
+ * set to <code>nil</code> or <code>false</code>, and a String if not.
+ */
+ private RubyString getCreateId(ThreadContext context) {
+ IRubyObject v = info.jsonModule.callMethod(context, "create_id");
+ return v.isTrue() ? v.convertToString() : null;
+ }
+
+ /**
+ * A string parsing session.
+ *
+ * <p>Once a ParserSession is instantiated, the source string should not
+ * change until the parsing is complete. The ParserSession object assumes
+ * the source {@link RubyString} is still associated to its original
+ * {@link ByteList}, which in turn must still be bound to the same
+ * <code>byte[]</code> value (and on the same offset).
+ */
+ // Ragel uses lots of fall-through
+ @SuppressWarnings("fallthrough")
+ private static class ParserSession {
+ private final Parser parser;
+ private final ThreadContext context;
+ private final ByteList byteList;
+ private final byte[] data;
+ private final StringDecoder decoder;
+ private int currentNesting = 0;
+
+ // 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) {
+ this.parser = parser;
+ this.context = context;
+ this.byteList = parser.vSource.getByteList();
+ this.data = byteList.unsafeBytes();
+ this.decoder = new StringDecoder(context);
+ }
+
+ private RaiseException unexpectedToken(int absStart, int absEnd) {
+ RubyString msg = getRuntime().newString("unexpected token at '")
+ .cat(data, absStart, absEnd - absStart)
+ .cat((byte)'\'');
+ return newException(Utils.M_PARSER_ERROR, msg);
+ }
+
+ private Ruby getRuntime() {
+ return context.getRuntime();
+ }
+
+ %%{
+ machine JSON_common;
+
+ cr = '\n';
+ cr_neg = [^\n];
+ ws = [ \t\r\n];
+ c_comment = '/*' ( any* - (any* '*/' any* ) ) '*/';
+ cpp_comment = '//' cr_neg* cr;
+ comment = c_comment | cpp_comment;
+ ignore = ws | comment;
+ name_separator = ':';
+ value_separator = ',';
+ Vnull = 'null';
+ Vfalse = 'false';
+ Vtrue = 'true';
+ VNaN = 'NaN';
+ VInfinity = 'Infinity';
+ VMinusInfinity = '-Infinity';
+ begin_value = [nft"\-[{NI] | digit;
+ begin_object = '{';
+ end_object = '}';
+ begin_array = '[';
+ end_array = ']';
+ begin_string = '"';
+ begin_name = begin_string;
+ begin_number = digit | '-';
+ }%%
+
+ %%{
+ machine JSON_value;
+ include JSON_common;
+
+ write data;
+
+ action parse_null {
+ result = getRuntime().getNil();
+ }
+ action parse_false {
+ result = getRuntime().getFalse();
+ }
+ action parse_true {
+ result = getRuntime().getTrue();
+ }
+ action parse_nan {
+ if (parser.allowNaN) {
+ result = getConstant(CONST_NAN);
+ } else {
+ throw unexpectedToken(p - 2, pe);
+ }
+ }
+ action parse_infinity {
+ if (parser.allowNaN) {
+ result = getConstant(CONST_INFINITY);
+ } else {
+ throw unexpectedToken(p - 7, pe);
+ }
+ }
+ action parse_number {
+ if (pe > fpc + 9 &&
+ absSubSequence(fpc, fpc + 9).toString().equals(JSON_MINUS_INFINITY)) {
+
+ if (parser.allowNaN) {
+ result = getConstant(CONST_MINUS_INFINITY);
+ fexec p + 10;
+ fhold;
+ fbreak;
+ } else {
+ throw unexpectedToken(p, pe);
+ }
+ }
+ ParserResult res = parseFloat(fpc, pe);
+ if (res != null) {
+ result = res.result;
+ fexec res.p;
+ }
+ res = parseInteger(fpc, pe);
+ if (res != null) {
+ result = res.result;
+ fexec res.p;
+ }
+ fhold;
+ fbreak;
+ }
+ action parse_string {
+ ParserResult res = parseString(fpc, pe);
+ if (res == null) {
+ fhold;
+ fbreak;
+ } else {
+ result = res.result;
+ fexec res.p;
+ }
+ }
+ action parse_array {
+ currentNesting++;
+ ParserResult res = parseArray(fpc, pe);
+ currentNesting--;
+ if (res == null) {
+ fhold;
+ fbreak;
+ } else {
+ result = res.result;
+ fexec res.p;
+ }
+ }
+ action parse_object {
+ currentNesting++;
+ ParserResult res = parseObject(fpc, pe);
+ currentNesting--;
+ if (res == null) {
+ fhold;
+ fbreak;
+ } else {
+ result = res.result;
+ fexec res.p;
+ }
+ }
+ action exit {
+ fhold;
+ fbreak;
+ }
+
+ main := ( Vnull @parse_null |
+ Vfalse @parse_false |
+ Vtrue @parse_true |
+ VNaN @parse_nan |
+ VInfinity @parse_infinity |
+ begin_number >parse_number |
+ begin_string >parse_string |
+ begin_array >parse_array |
+ begin_object >parse_object
+ ) %*exit;
+ }%%
+
+ ParserResult parseValue(int p, int pe) {
+ int cs = EVIL;
+ IRubyObject result = null;
+
+ %% write init;
+ %% write exec;
+
+ if (cs >= JSON_value_first_final && result != null) {
+ return new ParserResult(result, p);
+ } else {
+ return null;
+ }
+ }
+
+ %%{
+ machine JSON_integer;
+
+ write data;
+
+ action exit {
+ fhold;
+ fbreak;
+ }
+
+ main := '-'? ( '0' | [1-9][0-9]* ) ( ^[0-9] @exit );
+ }%%
+
+ ParserResult parseInteger(int p, int pe) {
+ int cs = EVIL;
+
+ %% write init;
+ int memo = p;
+ %% write exec;
+
+ if (cs < JSON_integer_first_final) {
+ return null;
+ }
+
+ ByteList num = absSubSequence(memo, p);
+ // note: this is actually a shared string, but since it is temporary and
+ // read-only, it doesn't really matter
+ RubyString expr = RubyString.newStringLight(getRuntime(), num);
+ RubyInteger number = RubyNumeric.str2inum(getRuntime(), expr, 10, true);
+ return new ParserResult(number, p + 1);
+ }
+
+ %%{
+ machine JSON_float;
+ include JSON_common;
+
+ write data;
+
+ action exit {
+ fhold;
+ fbreak;
+ }
+
+ main := '-'?
+ ( ( ( '0' | [1-9][0-9]* ) '.' [0-9]+ ( [Ee] [+\-]?[0-9]+ )? )
+ | ( ( '0' | [1-9][0-9]* ) ( [Ee] [+\-]? [0-9]+ ) ) )
+ ( ^[0-9Ee.\-] @exit );
+ }%%
+
+ ParserResult parseFloat(int p, int pe) {
+ int cs = EVIL;
+
+ %% write init;
+ int memo = p;
+ %% write exec;
+
+ if (cs < JSON_float_first_final) {
+ return null;
+ }
+
+ ByteList num = absSubSequence(memo, p);
+ // note: this is actually a shared string, but since it is temporary and
+ // read-only, it doesn't really matter
+ RubyString expr = RubyString.newStringLight(getRuntime(), num);
+ RubyFloat number = RubyNumeric.str2fnum(getRuntime(), expr, true);
+ return new ParserResult(number, p + 1);
+ }
+
+ %%{
+ machine JSON_string;
+ include JSON_common;
+
+ write data;
+
+ action parse_string {
+ int offset = byteList.begin();
+ ByteList decoded = decoder.decode(byteList, memo + 1 - offset,
+ p - offset);
+ result = getRuntime().newString(decoded);
+ if (result == null) {
+ fhold;
+ fbreak;
+ } else {
+ fexec p + 1;
+ }
+ }
+
+ action exit {
+ fhold;
+ fbreak;
+ }
+
+ main := '"'
+ ( ( ^(["\\]|0..0x1f)
+ | '\\'["\\/bfnrt]
+ | '\\u'[0-9a-fA-F]{4}
+ | '\\'^(["\\/bfnrtu]|0..0x1f)
+ )* %parse_string
+ ) '"' @exit;
+ }%%
+
+ ParserResult parseString(int p, int pe) {
+ int cs = EVIL;
+ RubyString result = null;
+
+ %% write init;
+ int memo = p;
+ %% write exec;
+
+ if (cs >= JSON_string_first_final && result != null) {
+ return new ParserResult(result, p + 1);
+ } else {
+ return null;
+ }
+ }
+
+ %%{
+ machine JSON_array;
+ include JSON_common;
+
+ write data;
+
+ action parse_value {
+ ParserResult res = parseValue(fpc, pe);
+ if (res == null) {
+ fhold;
+ fbreak;
+ } else {
+ result.append(res.result);
+ fexec res.p;
+ }
+ }
+
+ action exit {
+ fhold;
+ fbreak;
+ }
+
+ next_element = value_separator ignore* begin_value >parse_value;
+
+ main := begin_array
+ ignore*
+ ( ( begin_value >parse_value
+ ignore* )
+ ( ignore*
+ next_element
+ ignore* )* )?
+ ignore*
+ end_array @exit;
+ }%%
+
+ ParserResult parseArray(int p, int pe) {
+ int cs = EVIL;
+
+ if (parser.maxNesting > 0 && currentNesting > parser.maxNesting) {
+ throw newException(Utils.M_NESTING_ERROR,
+ "nesting of " + currentNesting + " is too deep");
+ }
+
+ // this is guaranteed to be a RubyArray due to the earlier
+ // allocator test at OptionsReader#getClass
+ RubyArray result =
+ (RubyArray)parser.arrayClass.newInstance(context,
+ IRubyObject.NULL_ARRAY, Block.NULL_BLOCK);
+
+ %% write init;
+ %% write exec;
+
+ if (cs >= JSON_array_first_final) {
+ return new ParserResult(result, p + 1);
+ } else {
+ throw unexpectedToken(p, pe);
+ }
+ }
+
+ %%{
+ machine JSON_object;
+ include JSON_common;
+
+ write data;
+
+ action parse_value {
+ ParserResult res = parseValue(fpc, pe);
+ if (res == null) {
+ fhold;
+ fbreak;
+ } else {
+ result.op_aset(context, lastName, res.result);
+ fexec res.p;
+ }
+ }
+
+ action parse_name {
+ ParserResult res = parseString(fpc, pe);
+ if (res == null) {
+ fhold;
+ fbreak;
+ } else {
+ RubyString name = (RubyString)res.result;
+ if (parser.symbolizeNames) {
+ lastName = context.getRuntime().is1_9()
+ ? name.intern19()
+ : name.intern();
+ } else {
+ lastName = name;
+ }
+ fexec res.p;
+ }
+ }
+
+ action exit {
+ fhold;
+ fbreak;
+ }
+
+ a_pair = ignore*
+ begin_name >parse_name
+ ignore* name_separator ignore*
+ begin_value >parse_value;
+
+ main := begin_object
+ (a_pair (ignore* value_separator a_pair)*)?
+ ignore* end_object @exit;
+ }%%
+
+ ParserResult parseObject(int p, int pe) {
+ int cs = EVIL;
+ IRubyObject lastName = null;
+
+ if (parser.maxNesting > 0 && currentNesting > parser.maxNesting) {
+ throw newException(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
+ RubyHash result =
+ (RubyHash)parser.objectClass.newInstance(context,
+ IRubyObject.NULL_ARRAY, Block.NULL_BLOCK);
+
+ %% write init;
+ %% write exec;
+
+ if (cs < JSON_object_first_final) {
+ return null;
+ }
+
+ IRubyObject returnedResult = result;
+
+ // attempt to de-serialize object
+ if (parser.createId != null) {
+ IRubyObject vKlassName = result.op_aref(context, parser.createId);
+ if (!vKlassName.isNil()) {
+ // might throw ArgumentError, we let it propagate
+ IRubyObject klass = parser.info.jsonModule.
+ callMethod(context, "deep_const_get", vKlassName);
+ if (klass.respondsTo("json_creatable?") &&
+ klass.callMethod(context, "json_creatable?").isTrue()) {
+
+ returnedResult = klass.callMethod(context, "json_create", result);
+ }
+ }
+ }
+ return new ParserResult(returnedResult, p + 1);
+ }
+
+ %%{
+ machine JSON;
+ include JSON_common;
+
+ write data;
+
+ action parse_object {
+ currentNesting = 1;
+ ParserResult res = parseObject(fpc, pe);
+ if (res == null) {
+ fhold;
+ fbreak;
+ } else {
+ result = res.result;
+ fexec res.p;
+ }
+ }
+
+ action parse_array {
+ currentNesting = 1;
+ ParserResult res = parseArray(fpc, pe);
+ if (res == null) {
+ fhold;
+ fbreak;
+ } else {
+ result = res.result;
+ fexec res.p;
+ }
+ }
+
+ main := ignore*
+ ( begin_object >parse_object
+ | begin_array >parse_array )
+ ignore*;
+ }%%
+
+ public IRubyObject parse() {
+ int cs = EVIL;
+ int p, pe;
+ IRubyObject result = null;
+
+ %% write init;
+ p = byteList.begin();
+ pe = p + byteList.length();
+ %% write exec;
+
+ if (cs >= JSON_first_final && p == pe) {
+ return result;
+ } else {
+ throw unexpectedToken(p, pe);
+ }
+ }
+
+ /**
+ * Returns a subsequence of the source ByteList, based on source
+ * array byte offsets (i.e., the ByteList's own begin offset is not
+ * automatically added).
+ * @param start
+ * @param end
+ */
+ private ByteList absSubSequence(int absStart, int absEnd) {
+ int offset = byteList.begin();
+ return (ByteList)byteList.subSequence(absStart - offset,
+ absEnd - offset);
+ }
+
+ /**
+ * Retrieves a constant directly descended from the <code>JSON</code> module.
+ * @param name The constant name
+ */
+ private IRubyObject getConstant(String name) {
+ return parser.info.jsonModule.getConstant(name);
+ }
+
+ private RaiseException newException(String className, String message) {
+ return Utils.newException(context, className, message);
+ }
+
+ private RaiseException newException(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));
+ }
+ }
+}
diff --git a/java/src/json/ext/ParserService.java b/java/src/json/ext/ParserService.java
new file mode 100644
index 0000000..e0805a7
--- /dev/null
+++ b/java/src/json/ext/ParserService.java
@@ -0,0 +1,34 @@
+/*
+ * This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
+ *
+ * Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
+ * for details.
+ */
+package json.ext;
+
+import java.io.IOException;
+
+import org.jruby.Ruby;
+import org.jruby.RubyClass;
+import org.jruby.RubyModule;
+import org.jruby.runtime.load.BasicLibraryService;
+
+/**
+ * The service invoked by JRuby's {@link org.jruby.runtime.load.LoadService LoadService}.
+ * Defines the <code>JSON::Ext::Parser</code> class.
+ * @author mernen
+ */
+public class ParserService implements BasicLibraryService {
+ public boolean basicLoad(Ruby runtime) throws IOException {
+ runtime.getLoadService().require("json/common");
+ RuntimeInfo info = RuntimeInfo.initRuntime(runtime);
+
+ info.jsonModule = runtime.defineModule("JSON");
+ RubyModule jsonExtModule = info.jsonModule.defineModuleUnder("Ext");
+ RubyClass parserClass =
+ jsonExtModule.defineClassUnder("Parser", runtime.getObject(),
+ Parser.ALLOCATOR);
+ parserClass.defineAnnotatedMethods(Parser.class);
+ return true;
+ }
+}
diff --git a/java/src/json/ext/RuntimeInfo.java b/java/src/json/ext/RuntimeInfo.java
new file mode 100644
index 0000000..f446afe
--- /dev/null
+++ b/java/src/json/ext/RuntimeInfo.java
@@ -0,0 +1,119 @@
+/*
+ * This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
+ *
+ * Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
+ * for details.
+ */
+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;
+
+
+final class RuntimeInfo {
+ // since the vast majority of cases runs just one runtime,
+ // we optimize for that
+ private static WeakReference<Ruby> runtime1 = new WeakReference<Ruby>(null);
+ private static RuntimeInfo info1;
+ // store remaining runtimes here (does not include runtime1)
+ private static Map<Ruby, RuntimeInfo> runtimes;
+
+ // these fields are filled by the service loaders
+ /** JSON */
+ RubyModule jsonModule;
+ /** JSON::Ext::Generator::GeneratorMethods::String::Extend */
+ RubyModule stringExtendModule;
+ /** JSON::Ext::Generator::State */
+ RubyClass generatorStateClass;
+ /** JSON::SAFE_STATE_PROTOTYPE */
+ GeneratorState safeStatePrototype;
+
+ final RubyEncoding utf8;
+ final RubyEncoding ascii8bit;
+ // other encodings
+ private final Map<String, RubyEncoding> 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 = (RubyEncoding)RubyEncoding.find(context,
+ encodingClass, runtime.newString("utf-8"));
+ ascii8bit = (RubyEncoding)RubyEncoding.find(context,
+ encodingClass, runtime.newString("ascii-8bit"));
+ encodings = new HashMap<String, RubyEncoding>();
+ }
+ }
+
+ static RuntimeInfo initRuntime(Ruby runtime) {
+ synchronized (RuntimeInfo.class) {
+ if (runtime1.get() == runtime) {
+ return info1;
+ } else if (runtime1.get() == null) {
+ runtime1 = new WeakReference<Ruby>(runtime);
+ info1 = new RuntimeInfo(runtime);
+ return info1;
+ } else {
+ if (runtimes == null) {
+ runtimes = new WeakHashMap<Ruby, RuntimeInfo>(1);
+ }
+ RuntimeInfo cache = runtimes.get(runtime);
+ if (cache == null) {
+ cache = new RuntimeInfo(runtime);
+ runtimes.put(runtime, cache);
+ }
+ return cache;
+ }
+ }
+ }
+
+ public static RuntimeInfo forRuntime(Ruby runtime) {
+ synchronized (RuntimeInfo.class) {
+ if (runtime1.get() == runtime) return info1;
+ RuntimeInfo cache = null;
+ if (runtimes != null) cache = runtimes.get(runtime);
+ assert cache != null : "Runtime given has not initialized JSON::Ext";
+ return cache;
+ }
+ }
+
+ public boolean encodingsSupported() {
+ return utf8 != null;
+ }
+
+ public RubyEncoding getEncoding(ThreadContext context, String name) {
+ synchronized (encodings) {
+ RubyEncoding encoding = encodings.get(name);
+ if (encoding == null) {
+ Ruby runtime = context.getRuntime();
+ encoding = (RubyEncoding)RubyEncoding.find(context,
+ runtime.getEncoding(), runtime.newString(name));
+ encodings.put(name, encoding);
+ }
+ return encoding;
+ }
+ }
+
+ public GeneratorState getSafeStatePrototype(ThreadContext context) {
+ if (safeStatePrototype == null) {
+ IRubyObject value = jsonModule.getConstant("SAFE_STATE_PROTOTYPE");
+ if (!(value instanceof GeneratorState)) {
+ throw context.getRuntime().newTypeError(value, generatorStateClass);
+ }
+ safeStatePrototype = (GeneratorState)value;
+ }
+ return safeStatePrototype;
+ }
+}
diff --git a/java/src/json/ext/StringDecoder.java b/java/src/json/ext/StringDecoder.java
new file mode 100644
index 0000000..a4ee975
--- /dev/null
+++ b/java/src/json/ext/StringDecoder.java
@@ -0,0 +1,166 @@
+/*
+ * This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
+ *
+ * Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
+ * for details.
+ */
+package json.ext;
+
+import org.jruby.exceptions.RaiseException;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.util.ByteList;
+
+/**
+ * A decoder that reads a JSON-encoded string from the given sources and
+ * returns its decoded form on a new ByteList. Escaped Unicode characters
+ * are encoded as UTF-8.
+ */
+final class StringDecoder extends ByteListTranscoder {
+ /**
+ * Stores the offset of the high surrogate when reading a surrogate pair,
+ * or -1 when not.
+ */
+ private int surrogatePairStart = -1;
+
+ // Array used for writing multi-byte characters into the buffer at once
+ private final byte[] aux = new byte[4];
+
+ StringDecoder(ThreadContext context) {
+ super(context);
+ }
+
+ ByteList decode(ByteList src, int start, int end) {
+ ByteList out = new ByteList(end - start);
+ init(src, start, end, out);
+ while (hasNext()) {
+ handleChar(readUtf8Char());
+ }
+ quoteStop(pos);
+ return out;
+ }
+
+ private void handleChar(int c) {
+ if (c == '\\') {
+ quoteStop(charStart);
+ handleEscapeSequence();
+ } else {
+ quoteStart();
+ }
+ }
+
+ private void handleEscapeSequence() {
+ ensureMin(1);
+ switch (readUtf8Char()) {
+ case 'b':
+ append('\b');
+ break;
+ case 'f':
+ append('\f');
+ break;
+ case 'n':
+ append('\n');
+ break;
+ case 'r':
+ append('\r');
+ break;
+ case 't':
+ append('\t');
+ break;
+ case 'u':
+ ensureMin(4);
+ int cp = readHex();
+ if (Character.isHighSurrogate((char)cp)) {
+ handleLowSurrogate((char)cp);
+ } else if (Character.isLowSurrogate((char)cp)) {
+ // low surrogate with no high surrogate
+ throw invalidUtf8();
+ } else {
+ writeUtf8Char(cp);
+ }
+ break;
+ default: // '\\', '"', '/'...
+ quoteStart();
+ }
+ }
+
+ private void handleLowSurrogate(char highSurrogate) {
+ surrogatePairStart = charStart;
+ ensureMin(1);
+ int lowSurrogate = readUtf8Char();
+
+ if (lowSurrogate == '\\') {
+ ensureMin(5);
+ if (readUtf8Char() != 'u') throw invalidUtf8();
+ lowSurrogate = readHex();
+ }
+
+ if (Character.isLowSurrogate((char)lowSurrogate)) {
+ writeUtf8Char(Character.toCodePoint(highSurrogate,
+ (char)lowSurrogate));
+ surrogatePairStart = -1;
+ } else {
+ throw invalidUtf8();
+ }
+ }
+
+ private void writeUtf8Char(int codePoint) {
+ if (codePoint < 0x80) {
+ append(codePoint);
+ } else if (codePoint < 0x800) {
+ aux[0] = (byte)(0xc0 | (codePoint >>> 6));
+ aux[1] = tailByte(codePoint & 0x3f);
+ append(aux, 0, 2);
+ } else if (codePoint < 0x10000) {
+ aux[0] = (byte)(0xe0 | (codePoint >>> 12));
+ aux[1] = tailByte(codePoint >>> 6);
+ aux[2] = tailByte(codePoint);
+ append(aux, 0, 3);
+ } else {
+ aux[0] = (byte)(0xf0 | codePoint >>> 18);
+ aux[1] = tailByte(codePoint >>> 12);
+ aux[2] = tailByte(codePoint >>> 6);
+ aux[3] = tailByte(codePoint);
+ append(aux, 0, 4);
+ }
+ }
+
+ private byte tailByte(int value) {
+ return (byte)(0x80 | (value & 0x3f));
+ }
+
+ /**
+ * Reads a 4-digit unsigned hexadecimal number from the source.
+ */
+ private int readHex() {
+ int numberStart = pos;
+ int result = 0;
+ int length = 4;
+ for (int i = 0; i < length; i++) {
+ int digit = readUtf8Char();
+ int digitValue;
+ if (digit >= '0' && digit <= '9') {
+ digitValue = digit - '0';
+ } else if (digit >= 'a' && digit <= 'f') {
+ digitValue = 10 + digit - 'a';
+ } else if (digit >= 'A' && digit <= 'F') {
+ digitValue = 10 + digit - 'A';
+ } else {
+ throw new NumberFormatException("Invalid base 16 number "
+ + src.subSequence(numberStart, numberStart + length));
+ }
+ result = result * 16 + digitValue;
+ }
+ return result;
+ }
+
+ @Override
+ protected RaiseException invalidUtf8() {
+ 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));
+ }
+}
diff --git a/java/src/json/ext/StringEncoder.java b/java/src/json/ext/StringEncoder.java
new file mode 100644
index 0000000..57bd19b
--- /dev/null
+++ b/java/src/json/ext/StringEncoder.java
@@ -0,0 +1,106 @@
+package json.ext;
+
+import org.jruby.exceptions.RaiseException;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.util.ByteList;
+
+/**
+ * An encoder that reads from the given source and outputs its representation
+ * to another ByteList. The source string is fully checked for UTF-8 validity,
+ * and throws a GeneratorError if any problem is found.
+ */
+final class StringEncoder extends ByteListTranscoder {
+ private final boolean asciiOnly;
+
+ // 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 */
+ '\\', 'u', 0, 0, 0, 0,
+ /* Second unicode character (for surrogate pairs) */
+ '\\', 'u', 0, 0, 0, 0,
+ /* "\X" characters */
+ '\\', 0};
+ // offsets on the array above
+ private static final int ESCAPE_UNI1_OFFSET = 0;
+ private static final int ESCAPE_UNI2_OFFSET = ESCAPE_UNI1_OFFSET + 6;
+ private static final int ESCAPE_CHAR_OFFSET = ESCAPE_UNI2_OFFSET + 6;
+ /** Array used for code point decomposition in surrogates */
+ private final char[] utf16 = new char[2];
+
+ private static final byte[] HEX =
+ new byte[] {'0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+ StringEncoder(ThreadContext context, boolean asciiOnly) {
+ super(context);
+ this.asciiOnly = asciiOnly;
+ }
+
+ void encode(ByteList src, ByteList out) {
+ init(src, out);
+ append('"');
+ while (hasNext()) {
+ handleChar(readUtf8Char());
+ }
+ quoteStop(pos);
+ append('"');
+ }
+
+ private void handleChar(int c) {
+ switch (c) {
+ case '"':
+ case '\\':
+ escapeChar((char)c);
+ break;
+ case '\n':
+ escapeChar('n');
+ break;
+ case '\r':
+ escapeChar('r');
+ break;
+ case '\t':
+ escapeChar('t');
+ break;
+ case '\f':
+ escapeChar('f');
+ break;
+ case '\b':
+ escapeChar('b');
+ break;
+ default:
+ if (c >= 0x20 && c <= 0x7f ||
+ (c >= 0x80 && !asciiOnly)) {
+ quoteStart();
+ } else {
+ quoteStop(charStart);
+ escapeUtf8Char(c);
+ }
+ }
+ }
+
+ private void escapeChar(char c) {
+ quoteStop(charStart);
+ aux[ESCAPE_CHAR_OFFSET + 1] = (byte)c;
+ append(aux, ESCAPE_CHAR_OFFSET, 2);
+ }
+
+ private void escapeUtf8Char(int codePoint) {
+ int numChars = Character.toChars(codePoint, utf16, 0);
+ escapeCodeUnit(utf16[0], ESCAPE_UNI1_OFFSET + 2);
+ if (numChars > 1) escapeCodeUnit(utf16[1], ESCAPE_UNI2_OFFSET + 2);
+ append(aux, ESCAPE_UNI1_OFFSET, 6 * numChars);
+ }
+
+ private void escapeCodeUnit(char c, int auxOffset) {
+ for (int i = 0; i < 4; i++) {
+ aux[auxOffset + i] = HEX[(c >>> (12 - 4 * i)) & 0xf];
+ }
+ }
+
+ @Override
+ protected RaiseException invalidUtf8() {
+ 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
new file mode 100644
index 0000000..7a1dfee
--- /dev/null
+++ b/java/src/json/ext/Utils.java
@@ -0,0 +1,89 @@
+/*
+ * This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
+ *
+ * Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
+ * for details.
+ */
+package json.ext;
+
+import org.jruby.Ruby;
+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;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+import org.jruby.util.ByteList;
+
+/**
+ * Library of miscellaneous utility functions
+ */
+final class Utils {
+ public static final String M_GENERATOR_ERROR = "GeneratorError";
+ public static final String M_NESTING_ERROR = "NestingError";
+ public static final String M_PARSER_ERROR = "ParserError";
+
+ private Utils() {
+ throw new RuntimeException();
+ }
+
+ /**
+ * Safe {@link RubyArray} type-checking.
+ * Returns the given object if it is an <code>Array</code>,
+ * or throws an exception if not.
+ * @param object The object to test
+ * @return The given object if it is an <code>Array</code>
+ * @throws RaiseException <code>TypeError</code> if the object is not
+ * of the expected type
+ */
+ static RubyArray ensureArray(IRubyObject object) throws RaiseException {
+ if (object instanceof RubyArray) return (RubyArray)object;
+ Ruby runtime = object.getRuntime();
+ 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();
+ throw runtime.newTypeError(object, runtime.getString());
+ }
+
+ static RaiseException newException(ThreadContext context,
+ String className, String message) {
+ return newException(context, className,
+ context.getRuntime().newString(message));
+ }
+
+ static RaiseException newException(ThreadContext context,
+ String className, RubyString message) {
+ RuntimeInfo info = RuntimeInfo.forRuntime(context.getRuntime());
+ RubyClass klazz = info.jsonModule.getClass(className);
+ RubyException excptn =
+ (RubyException)klazz.newInstance(context,
+ new IRubyObject[] {message}, Block.NULL_BLOCK);
+ return new RaiseException(excptn);
+ }
+
+ static byte[] repeat(ByteList a, int n) {
+ return repeat(a.unsafeBytes(), a.begin(), a.length(), n);
+ }
+
+ static byte[] repeat(byte[] a, int begin, int length, int n) {
+ if (length == 0) return ByteList.NULL_ARRAY;
+ int resultLen = length * n;
+ byte[] result = new byte[resultLen];
+ for (int pos = 0; pos < resultLen; pos += length) {
+ System.arraycopy(a, begin, result, pos, length);
+ }
+ return result;
+ }
+}