json 1.4.6 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of json might be problematic. Click here for more details.

Files changed (48) hide show
  1. data/CHANGES +6 -0
  2. data/COPYING-json-jruby +57 -0
  3. data/README-json-jruby.markdown +33 -0
  4. data/Rakefile +224 -119
  5. data/VERSION +1 -1
  6. data/benchmarks/generator2_benchmark.rb +1 -1
  7. data/benchmarks/generator_benchmark.rb +1 -1
  8. data/ext/json/ext/generator/generator.c +20 -20
  9. data/ext/json/ext/generator/generator.h +7 -7
  10. data/ext/json/ext/parser/extconf.rb +1 -0
  11. data/ext/json/ext/parser/parser.c +122 -88
  12. data/ext/json/ext/parser/parser.h +7 -0
  13. data/ext/json/ext/parser/parser.rl +54 -20
  14. data/java/lib/bytelist-1.0.6.jar +0 -0
  15. data/java/lib/jcodings.jar +0 -0
  16. data/java/src/json/ext/ByteListTranscoder.java +167 -0
  17. data/java/src/json/ext/Generator.java +441 -0
  18. data/java/src/json/ext/GeneratorMethods.java +231 -0
  19. data/java/src/json/ext/GeneratorService.java +42 -0
  20. data/java/src/json/ext/GeneratorState.java +473 -0
  21. data/java/src/json/ext/OptionsReader.java +119 -0
  22. data/java/src/json/ext/Parser.java +2295 -0
  23. data/java/src/json/ext/Parser.rl +825 -0
  24. data/java/src/json/ext/ParserService.java +34 -0
  25. data/java/src/json/ext/RuntimeInfo.java +119 -0
  26. data/java/src/json/ext/StringDecoder.java +166 -0
  27. data/java/src/json/ext/StringEncoder.java +106 -0
  28. data/java/src/json/ext/Utils.java +89 -0
  29. data/json-java.gemspec +20 -0
  30. data/lib/json/add/core.rb +1 -2
  31. data/lib/json/add/rails.rb +4 -54
  32. data/lib/json/common.rb +36 -8
  33. data/lib/json/editor.rb +1 -3
  34. data/lib/json/ext.rb +2 -2
  35. data/lib/json/pure.rb +2 -64
  36. data/lib/json/pure/generator.rb +10 -8
  37. data/lib/json/pure/parser.rb +23 -12
  38. data/lib/json/version.rb +1 -1
  39. data/tests/setup_variant.rb +11 -0
  40. data/tests/test_json.rb +1 -5
  41. data/tests/test_json_addition.rb +14 -9
  42. data/tests/test_json_encoding.rb +9 -12
  43. data/tests/test_json_fixtures.rb +9 -8
  44. data/tests/test_json_generate.rb +3 -5
  45. data/tests/test_json_string_matching.rb +40 -0
  46. data/tests/test_json_unicode.rb +1 -5
  47. metadata +51 -13
  48. data/tests/test_json_rails.rb +0 -144
@@ -0,0 +1,34 @@
1
+ /*
2
+ * This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
3
+ *
4
+ * Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
5
+ * for details.
6
+ */
7
+ package json.ext;
8
+
9
+ import java.io.IOException;
10
+
11
+ import org.jruby.Ruby;
12
+ import org.jruby.RubyClass;
13
+ import org.jruby.RubyModule;
14
+ import org.jruby.runtime.load.BasicLibraryService;
15
+
16
+ /**
17
+ * The service invoked by JRuby's {@link org.jruby.runtime.load.LoadService LoadService}.
18
+ * Defines the <code>JSON::Ext::Parser</code> class.
19
+ * @author mernen
20
+ */
21
+ public class ParserService implements BasicLibraryService {
22
+ public boolean basicLoad(Ruby runtime) throws IOException {
23
+ runtime.getLoadService().require("json/common");
24
+ RuntimeInfo info = RuntimeInfo.initRuntime(runtime);
25
+
26
+ info.jsonModule = runtime.defineModule("JSON");
27
+ RubyModule jsonExtModule = info.jsonModule.defineModuleUnder("Ext");
28
+ RubyClass parserClass =
29
+ jsonExtModule.defineClassUnder("Parser", runtime.getObject(),
30
+ Parser.ALLOCATOR);
31
+ parserClass.defineAnnotatedMethods(Parser.class);
32
+ return true;
33
+ }
34
+ }
@@ -0,0 +1,119 @@
1
+ /*
2
+ * This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
3
+ *
4
+ * Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
5
+ * for details.
6
+ */
7
+ package json.ext;
8
+
9
+ import java.lang.ref.WeakReference;
10
+ import java.util.HashMap;
11
+ import java.util.Map;
12
+ import java.util.WeakHashMap;
13
+ import org.jruby.Ruby;
14
+ import org.jruby.RubyClass;
15
+ import org.jruby.RubyEncoding;
16
+ import org.jruby.RubyModule;
17
+ import org.jruby.runtime.ThreadContext;
18
+ import org.jruby.runtime.builtin.IRubyObject;
19
+
20
+
21
+ final class RuntimeInfo {
22
+ // since the vast majority of cases runs just one runtime,
23
+ // we optimize for that
24
+ private static WeakReference<Ruby> runtime1 = new WeakReference<Ruby>(null);
25
+ private static RuntimeInfo info1;
26
+ // store remaining runtimes here (does not include runtime1)
27
+ private static Map<Ruby, RuntimeInfo> runtimes;
28
+
29
+ // these fields are filled by the service loaders
30
+ /** JSON */
31
+ RubyModule jsonModule;
32
+ /** JSON::Ext::Generator::GeneratorMethods::String::Extend */
33
+ RubyModule stringExtendModule;
34
+ /** JSON::Ext::Generator::State */
35
+ RubyClass generatorStateClass;
36
+ /** JSON::SAFE_STATE_PROTOTYPE */
37
+ GeneratorState safeStatePrototype;
38
+
39
+ final RubyEncoding utf8;
40
+ final RubyEncoding ascii8bit;
41
+ // other encodings
42
+ private final Map<String, RubyEncoding> encodings;
43
+
44
+ private RuntimeInfo(Ruby runtime) {
45
+ RubyClass encodingClass = runtime.getEncoding();
46
+ if (encodingClass == null) { // 1.8 mode
47
+ utf8 = ascii8bit = null;
48
+ encodings = null;
49
+ } else {
50
+ ThreadContext context = runtime.getCurrentContext();
51
+
52
+ utf8 = (RubyEncoding)RubyEncoding.find(context,
53
+ encodingClass, runtime.newString("utf-8"));
54
+ ascii8bit = (RubyEncoding)RubyEncoding.find(context,
55
+ encodingClass, runtime.newString("ascii-8bit"));
56
+ encodings = new HashMap<String, RubyEncoding>();
57
+ }
58
+ }
59
+
60
+ static RuntimeInfo initRuntime(Ruby runtime) {
61
+ synchronized (RuntimeInfo.class) {
62
+ if (runtime1.get() == runtime) {
63
+ return info1;
64
+ } else if (runtime1.get() == null) {
65
+ runtime1 = new WeakReference<Ruby>(runtime);
66
+ info1 = new RuntimeInfo(runtime);
67
+ return info1;
68
+ } else {
69
+ if (runtimes == null) {
70
+ runtimes = new WeakHashMap<Ruby, RuntimeInfo>(1);
71
+ }
72
+ RuntimeInfo cache = runtimes.get(runtime);
73
+ if (cache == null) {
74
+ cache = new RuntimeInfo(runtime);
75
+ runtimes.put(runtime, cache);
76
+ }
77
+ return cache;
78
+ }
79
+ }
80
+ }
81
+
82
+ public static RuntimeInfo forRuntime(Ruby runtime) {
83
+ synchronized (RuntimeInfo.class) {
84
+ if (runtime1.get() == runtime) return info1;
85
+ RuntimeInfo cache = null;
86
+ if (runtimes != null) cache = runtimes.get(runtime);
87
+ assert cache != null : "Runtime given has not initialized JSON::Ext";
88
+ return cache;
89
+ }
90
+ }
91
+
92
+ public boolean encodingsSupported() {
93
+ return utf8 != null;
94
+ }
95
+
96
+ public RubyEncoding getEncoding(ThreadContext context, String name) {
97
+ synchronized (encodings) {
98
+ RubyEncoding encoding = encodings.get(name);
99
+ if (encoding == null) {
100
+ Ruby runtime = context.getRuntime();
101
+ encoding = (RubyEncoding)RubyEncoding.find(context,
102
+ runtime.getEncoding(), runtime.newString(name));
103
+ encodings.put(name, encoding);
104
+ }
105
+ return encoding;
106
+ }
107
+ }
108
+
109
+ public GeneratorState getSafeStatePrototype(ThreadContext context) {
110
+ if (safeStatePrototype == null) {
111
+ IRubyObject value = jsonModule.getConstant("SAFE_STATE_PROTOTYPE");
112
+ if (!(value instanceof GeneratorState)) {
113
+ throw context.getRuntime().newTypeError(value, generatorStateClass);
114
+ }
115
+ safeStatePrototype = (GeneratorState)value;
116
+ }
117
+ return safeStatePrototype;
118
+ }
119
+ }
@@ -0,0 +1,166 @@
1
+ /*
2
+ * This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
3
+ *
4
+ * Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
5
+ * for details.
6
+ */
7
+ package json.ext;
8
+
9
+ import org.jruby.exceptions.RaiseException;
10
+ import org.jruby.runtime.ThreadContext;
11
+ import org.jruby.util.ByteList;
12
+
13
+ /**
14
+ * A decoder that reads a JSON-encoded string from the given sources and
15
+ * returns its decoded form on a new ByteList. Escaped Unicode characters
16
+ * are encoded as UTF-8.
17
+ */
18
+ final class StringDecoder extends ByteListTranscoder {
19
+ /**
20
+ * Stores the offset of the high surrogate when reading a surrogate pair,
21
+ * or -1 when not.
22
+ */
23
+ private int surrogatePairStart = -1;
24
+
25
+ // Array used for writing multi-byte characters into the buffer at once
26
+ private final byte[] aux = new byte[4];
27
+
28
+ StringDecoder(ThreadContext context) {
29
+ super(context);
30
+ }
31
+
32
+ ByteList decode(ByteList src, int start, int end) {
33
+ ByteList out = new ByteList(end - start);
34
+ init(src, start, end, out);
35
+ while (hasNext()) {
36
+ handleChar(readUtf8Char());
37
+ }
38
+ quoteStop(pos);
39
+ return out;
40
+ }
41
+
42
+ private void handleChar(int c) {
43
+ if (c == '\\') {
44
+ quoteStop(charStart);
45
+ handleEscapeSequence();
46
+ } else {
47
+ quoteStart();
48
+ }
49
+ }
50
+
51
+ private void handleEscapeSequence() {
52
+ ensureMin(1);
53
+ switch (readUtf8Char()) {
54
+ case 'b':
55
+ append('\b');
56
+ break;
57
+ case 'f':
58
+ append('\f');
59
+ break;
60
+ case 'n':
61
+ append('\n');
62
+ break;
63
+ case 'r':
64
+ append('\r');
65
+ break;
66
+ case 't':
67
+ append('\t');
68
+ break;
69
+ case 'u':
70
+ ensureMin(4);
71
+ int cp = readHex();
72
+ if (Character.isHighSurrogate((char)cp)) {
73
+ handleLowSurrogate((char)cp);
74
+ } else if (Character.isLowSurrogate((char)cp)) {
75
+ // low surrogate with no high surrogate
76
+ throw invalidUtf8();
77
+ } else {
78
+ writeUtf8Char(cp);
79
+ }
80
+ break;
81
+ default: // '\\', '"', '/'...
82
+ quoteStart();
83
+ }
84
+ }
85
+
86
+ private void handleLowSurrogate(char highSurrogate) {
87
+ surrogatePairStart = charStart;
88
+ ensureMin(1);
89
+ int lowSurrogate = readUtf8Char();
90
+
91
+ if (lowSurrogate == '\\') {
92
+ ensureMin(5);
93
+ if (readUtf8Char() != 'u') throw invalidUtf8();
94
+ lowSurrogate = readHex();
95
+ }
96
+
97
+ if (Character.isLowSurrogate((char)lowSurrogate)) {
98
+ writeUtf8Char(Character.toCodePoint(highSurrogate,
99
+ (char)lowSurrogate));
100
+ surrogatePairStart = -1;
101
+ } else {
102
+ throw invalidUtf8();
103
+ }
104
+ }
105
+
106
+ private void writeUtf8Char(int codePoint) {
107
+ if (codePoint < 0x80) {
108
+ append(codePoint);
109
+ } else if (codePoint < 0x800) {
110
+ aux[0] = (byte)(0xc0 | (codePoint >>> 6));
111
+ aux[1] = tailByte(codePoint & 0x3f);
112
+ append(aux, 0, 2);
113
+ } else if (codePoint < 0x10000) {
114
+ aux[0] = (byte)(0xe0 | (codePoint >>> 12));
115
+ aux[1] = tailByte(codePoint >>> 6);
116
+ aux[2] = tailByte(codePoint);
117
+ append(aux, 0, 3);
118
+ } else {
119
+ aux[0] = (byte)(0xf0 | codePoint >>> 18);
120
+ aux[1] = tailByte(codePoint >>> 12);
121
+ aux[2] = tailByte(codePoint >>> 6);
122
+ aux[3] = tailByte(codePoint);
123
+ append(aux, 0, 4);
124
+ }
125
+ }
126
+
127
+ private byte tailByte(int value) {
128
+ return (byte)(0x80 | (value & 0x3f));
129
+ }
130
+
131
+ /**
132
+ * Reads a 4-digit unsigned hexadecimal number from the source.
133
+ */
134
+ private int readHex() {
135
+ int numberStart = pos;
136
+ int result = 0;
137
+ int length = 4;
138
+ for (int i = 0; i < length; i++) {
139
+ int digit = readUtf8Char();
140
+ int digitValue;
141
+ if (digit >= '0' && digit <= '9') {
142
+ digitValue = digit - '0';
143
+ } else if (digit >= 'a' && digit <= 'f') {
144
+ digitValue = 10 + digit - 'a';
145
+ } else if (digit >= 'A' && digit <= 'F') {
146
+ digitValue = 10 + digit - 'A';
147
+ } else {
148
+ throw new NumberFormatException("Invalid base 16 number "
149
+ + src.subSequence(numberStart, numberStart + length));
150
+ }
151
+ result = result * 16 + digitValue;
152
+ }
153
+ return result;
154
+ }
155
+
156
+ @Override
157
+ protected RaiseException invalidUtf8() {
158
+ ByteList message = new ByteList(
159
+ ByteList.plain("partial character in source, " +
160
+ "but hit end near "));
161
+ int start = surrogatePairStart != -1 ? surrogatePairStart : charStart;
162
+ message.append(src, start, srcEnd - start);
163
+ return Utils.newException(context, Utils.M_PARSER_ERROR,
164
+ context.getRuntime().newString(message));
165
+ }
166
+ }
@@ -0,0 +1,106 @@
1
+ package json.ext;
2
+
3
+ import org.jruby.exceptions.RaiseException;
4
+ import org.jruby.runtime.ThreadContext;
5
+ import org.jruby.util.ByteList;
6
+
7
+ /**
8
+ * An encoder that reads from the given source and outputs its representation
9
+ * to another ByteList. The source string is fully checked for UTF-8 validity,
10
+ * and throws a GeneratorError if any problem is found.
11
+ */
12
+ final class StringEncoder extends ByteListTranscoder {
13
+ private final boolean asciiOnly;
14
+
15
+ // Escaped characters will reuse this array, to avoid new allocations
16
+ // or appending them byte-by-byte
17
+ private final byte[] aux =
18
+ new byte[] {/* First unicode character */
19
+ '\\', 'u', 0, 0, 0, 0,
20
+ /* Second unicode character (for surrogate pairs) */
21
+ '\\', 'u', 0, 0, 0, 0,
22
+ /* "\X" characters */
23
+ '\\', 0};
24
+ // offsets on the array above
25
+ private static final int ESCAPE_UNI1_OFFSET = 0;
26
+ private static final int ESCAPE_UNI2_OFFSET = ESCAPE_UNI1_OFFSET + 6;
27
+ private static final int ESCAPE_CHAR_OFFSET = ESCAPE_UNI2_OFFSET + 6;
28
+ /** Array used for code point decomposition in surrogates */
29
+ private final char[] utf16 = new char[2];
30
+
31
+ private static final byte[] HEX =
32
+ new byte[] {'0', '1', '2', '3', '4', '5', '6', '7',
33
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
34
+
35
+ StringEncoder(ThreadContext context, boolean asciiOnly) {
36
+ super(context);
37
+ this.asciiOnly = asciiOnly;
38
+ }
39
+
40
+ void encode(ByteList src, ByteList out) {
41
+ init(src, out);
42
+ append('"');
43
+ while (hasNext()) {
44
+ handleChar(readUtf8Char());
45
+ }
46
+ quoteStop(pos);
47
+ append('"');
48
+ }
49
+
50
+ private void handleChar(int c) {
51
+ switch (c) {
52
+ case '"':
53
+ case '\\':
54
+ escapeChar((char)c);
55
+ break;
56
+ case '\n':
57
+ escapeChar('n');
58
+ break;
59
+ case '\r':
60
+ escapeChar('r');
61
+ break;
62
+ case '\t':
63
+ escapeChar('t');
64
+ break;
65
+ case '\f':
66
+ escapeChar('f');
67
+ break;
68
+ case '\b':
69
+ escapeChar('b');
70
+ break;
71
+ default:
72
+ if (c >= 0x20 && c <= 0x7f ||
73
+ (c >= 0x80 && !asciiOnly)) {
74
+ quoteStart();
75
+ } else {
76
+ quoteStop(charStart);
77
+ escapeUtf8Char(c);
78
+ }
79
+ }
80
+ }
81
+
82
+ private void escapeChar(char c) {
83
+ quoteStop(charStart);
84
+ aux[ESCAPE_CHAR_OFFSET + 1] = (byte)c;
85
+ append(aux, ESCAPE_CHAR_OFFSET, 2);
86
+ }
87
+
88
+ private void escapeUtf8Char(int codePoint) {
89
+ int numChars = Character.toChars(codePoint, utf16, 0);
90
+ escapeCodeUnit(utf16[0], ESCAPE_UNI1_OFFSET + 2);
91
+ if (numChars > 1) escapeCodeUnit(utf16[1], ESCAPE_UNI2_OFFSET + 2);
92
+ append(aux, ESCAPE_UNI1_OFFSET, 6 * numChars);
93
+ }
94
+
95
+ private void escapeCodeUnit(char c, int auxOffset) {
96
+ for (int i = 0; i < 4; i++) {
97
+ aux[auxOffset + i] = HEX[(c >>> (12 - 4 * i)) & 0xf];
98
+ }
99
+ }
100
+
101
+ @Override
102
+ protected RaiseException invalidUtf8() {
103
+ return Utils.newException(context, Utils.M_GENERATOR_ERROR,
104
+ "source sequence is illegal/malformed utf-8");
105
+ }
106
+ }
@@ -0,0 +1,89 @@
1
+ /*
2
+ * This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
3
+ *
4
+ * Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
5
+ * for details.
6
+ */
7
+ package json.ext;
8
+
9
+ import org.jruby.Ruby;
10
+ import org.jruby.RubyArray;
11
+ import org.jruby.RubyClass;
12
+ import org.jruby.RubyException;
13
+ import org.jruby.RubyHash;
14
+ import org.jruby.RubyString;
15
+ import org.jruby.exceptions.RaiseException;
16
+ import org.jruby.runtime.Block;
17
+ import org.jruby.runtime.ThreadContext;
18
+ import org.jruby.runtime.builtin.IRubyObject;
19
+ import org.jruby.util.ByteList;
20
+
21
+ /**
22
+ * Library of miscellaneous utility functions
23
+ */
24
+ final class Utils {
25
+ public static final String M_GENERATOR_ERROR = "GeneratorError";
26
+ public static final String M_NESTING_ERROR = "NestingError";
27
+ public static final String M_PARSER_ERROR = "ParserError";
28
+
29
+ private Utils() {
30
+ throw new RuntimeException();
31
+ }
32
+
33
+ /**
34
+ * Safe {@link RubyArray} type-checking.
35
+ * Returns the given object if it is an <code>Array</code>,
36
+ * or throws an exception if not.
37
+ * @param object The object to test
38
+ * @return The given object if it is an <code>Array</code>
39
+ * @throws RaiseException <code>TypeError</code> if the object is not
40
+ * of the expected type
41
+ */
42
+ static RubyArray ensureArray(IRubyObject object) throws RaiseException {
43
+ if (object instanceof RubyArray) return (RubyArray)object;
44
+ Ruby runtime = object.getRuntime();
45
+ throw runtime.newTypeError(object, runtime.getArray());
46
+ }
47
+
48
+ static RubyHash ensureHash(IRubyObject object) throws RaiseException {
49
+ if (object instanceof RubyHash) return (RubyHash)object;
50
+ Ruby runtime = object.getRuntime();
51
+ throw runtime.newTypeError(object, runtime.getHash());
52
+ }
53
+
54
+ static RubyString ensureString(IRubyObject object) throws RaiseException {
55
+ if (object instanceof RubyString) return (RubyString)object;
56
+ Ruby runtime = object.getRuntime();
57
+ throw runtime.newTypeError(object, runtime.getString());
58
+ }
59
+
60
+ static RaiseException newException(ThreadContext context,
61
+ String className, String message) {
62
+ return newException(context, className,
63
+ context.getRuntime().newString(message));
64
+ }
65
+
66
+ static RaiseException newException(ThreadContext context,
67
+ String className, RubyString message) {
68
+ RuntimeInfo info = RuntimeInfo.forRuntime(context.getRuntime());
69
+ RubyClass klazz = info.jsonModule.getClass(className);
70
+ RubyException excptn =
71
+ (RubyException)klazz.newInstance(context,
72
+ new IRubyObject[] {message}, Block.NULL_BLOCK);
73
+ return new RaiseException(excptn);
74
+ }
75
+
76
+ static byte[] repeat(ByteList a, int n) {
77
+ return repeat(a.unsafeBytes(), a.begin(), a.length(), n);
78
+ }
79
+
80
+ static byte[] repeat(byte[] a, int begin, int length, int n) {
81
+ if (length == 0) return ByteList.NULL_ARRAY;
82
+ int resultLen = length * n;
83
+ byte[] result = new byte[resultLen];
84
+ for (int pos = 0; pos < resultLen; pos += length) {
85
+ System.arraycopy(a, begin, result, pos, length);
86
+ }
87
+ return result;
88
+ }
89
+ }