json_pure 1.4.6 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
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,231 @@
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.RubyBoolean;
12
+ import org.jruby.RubyFixnum;
13
+ import org.jruby.RubyFloat;
14
+ import org.jruby.RubyHash;
15
+ import org.jruby.RubyInteger;
16
+ import org.jruby.RubyModule;
17
+ import org.jruby.RubyNumeric;
18
+ import org.jruby.RubyString;
19
+ import org.jruby.anno.JRubyMethod;
20
+ import org.jruby.runtime.ThreadContext;
21
+ import org.jruby.runtime.builtin.IRubyObject;
22
+ import org.jruby.util.ByteList;
23
+
24
+ /**
25
+ * A class that populates the
26
+ * <code>Json::Ext::Generator::GeneratorMethods</code> module.
27
+ *
28
+ * @author mernen
29
+ */
30
+ class GeneratorMethods {
31
+ /**
32
+ * Populates the given module with all modules and their methods
33
+ * @param info
34
+ * @param generatorMethodsModule The module to populate
35
+ * (normally <code>JSON::Generator::GeneratorMethods</code>)
36
+ */
37
+ static void populate(RuntimeInfo info, RubyModule module) {
38
+ defineMethods(module, "Array", RbArray.class);
39
+ defineMethods(module, "FalseClass", RbFalse.class);
40
+ defineMethods(module, "Float", RbFloat.class);
41
+ defineMethods(module, "Hash", RbHash.class);
42
+ defineMethods(module, "Integer", RbInteger.class);
43
+ defineMethods(module, "NilClass", RbNil.class);
44
+ defineMethods(module, "Object", RbObject.class);
45
+ defineMethods(module, "String", RbString.class);
46
+ defineMethods(module, "TrueClass", RbTrue.class);
47
+
48
+ info.stringExtendModule = module.defineModuleUnder("String")
49
+ .defineModuleUnder("Extend");
50
+ info.stringExtendModule.defineAnnotatedMethods(StringExtend.class);
51
+ }
52
+
53
+ /**
54
+ * Convenience method for defining methods on a submodule.
55
+ * @param parentModule
56
+ * @param submoduleName
57
+ * @param klass
58
+ */
59
+ private static void defineMethods(RubyModule parentModule,
60
+ String submoduleName, Class klass) {
61
+ RubyModule submodule = parentModule.defineModuleUnder(submoduleName);
62
+ submodule.defineAnnotatedMethods(klass);
63
+ }
64
+
65
+
66
+ public static class RbHash {
67
+ @JRubyMethod(rest=true)
68
+ public static IRubyObject to_json(ThreadContext context,
69
+ IRubyObject vSelf, IRubyObject[] args) {
70
+ return Generator.generateJson(context, (RubyHash)vSelf,
71
+ Generator.HASH_HANDLER, args);
72
+ }
73
+ }
74
+
75
+ public static class RbArray {
76
+ @JRubyMethod(rest=true)
77
+ public static IRubyObject to_json(ThreadContext context,
78
+ IRubyObject vSelf, IRubyObject[] args) {
79
+ return Generator.generateJson(context, (RubyArray)vSelf,
80
+ Generator.ARRAY_HANDLER, args);
81
+ }
82
+ }
83
+
84
+ public static class RbInteger {
85
+ @JRubyMethod(rest=true)
86
+ public static IRubyObject to_json(ThreadContext context,
87
+ IRubyObject vSelf, IRubyObject[] args) {
88
+ return Generator.generateJson(context, vSelf, args);
89
+ }
90
+ }
91
+
92
+ public static class RbFloat {
93
+ @JRubyMethod(rest=true)
94
+ public static IRubyObject to_json(ThreadContext context,
95
+ IRubyObject vSelf, IRubyObject[] args) {
96
+ return Generator.generateJson(context, (RubyFloat)vSelf,
97
+ Generator.FLOAT_HANDLER, args);
98
+ }
99
+ }
100
+
101
+ public static class RbString {
102
+ @JRubyMethod(rest=true)
103
+ public static IRubyObject to_json(ThreadContext context,
104
+ IRubyObject vSelf, IRubyObject[] args) {
105
+ return Generator.generateJson(context, (RubyString)vSelf,
106
+ Generator.STRING_HANDLER, args);
107
+ }
108
+
109
+ /**
110
+ * <code>{@link RubyString String}#to_json_raw(*)</code>
111
+ *
112
+ * <p>This method creates a JSON text from the result of a call to
113
+ * {@link #to_json_raw_object} of this String.
114
+ */
115
+ @JRubyMethod(rest=true)
116
+ public static IRubyObject to_json_raw(ThreadContext context,
117
+ IRubyObject vSelf, IRubyObject[] args) {
118
+ RubyHash obj = toJsonRawObject(context, Utils.ensureString(vSelf));
119
+ return Generator.generateJson(context, obj,
120
+ Generator.HASH_HANDLER, args);
121
+ }
122
+
123
+ /**
124
+ * <code>{@link RubyString String}#to_json_raw_object(*)</code>
125
+ *
126
+ * <p>This method creates a raw object Hash, that can be nested into
127
+ * other data structures and will be unparsed as a raw string. This
128
+ * method should be used if you want to convert raw strings to JSON
129
+ * instead of UTF-8 strings, e.g. binary data.
130
+ */
131
+ @JRubyMethod(rest=true)
132
+ public static IRubyObject to_json_raw_object(ThreadContext context,
133
+ IRubyObject vSelf, IRubyObject[] args) {
134
+ return toJsonRawObject(context, Utils.ensureString(vSelf));
135
+ }
136
+
137
+ private static RubyHash toJsonRawObject(ThreadContext context,
138
+ RubyString self) {
139
+ Ruby runtime = context.getRuntime();
140
+ RubyHash result = RubyHash.newHash(runtime);
141
+
142
+ IRubyObject createId = RuntimeInfo.forRuntime(runtime)
143
+ .jsonModule.callMethod(context, "create_id");
144
+ result.op_aset(context, createId, self.getMetaClass().to_s());
145
+
146
+ ByteList bl = self.getByteList();
147
+ byte[] uBytes = bl.unsafeBytes();
148
+ RubyArray array = runtime.newArray(bl.length());
149
+ for (int i = bl.begin(), t = bl.begin() + bl.length(); i < t; i++) {
150
+ array.store(i, runtime.newFixnum(uBytes[i] & 0xff));
151
+ }
152
+
153
+ result.op_aset(context, runtime.newString("raw"), array);
154
+ return result;
155
+ }
156
+
157
+ @JRubyMethod(required=1, module=true)
158
+ public static IRubyObject included(ThreadContext context,
159
+ IRubyObject vSelf, IRubyObject module) {
160
+ RuntimeInfo info = RuntimeInfo.forRuntime(context.getRuntime());
161
+ return module.callMethod(context, "extend", info.stringExtendModule);
162
+ }
163
+ }
164
+
165
+ public static class StringExtend {
166
+ /**
167
+ * <code>{@link RubyString String}#json_create(o)</code>
168
+ *
169
+ * <p>Raw Strings are JSON Objects (the raw bytes are stored in an
170
+ * array for the key "raw"). The Ruby String can be created by this
171
+ * module method.
172
+ */
173
+ @JRubyMethod(required=1)
174
+ public static IRubyObject json_create(ThreadContext context,
175
+ IRubyObject vSelf, IRubyObject vHash) {
176
+ Ruby runtime = context.getRuntime();
177
+ RubyHash o = vHash.convertToHash();
178
+ IRubyObject rawData = o.fastARef(runtime.newString("raw"));
179
+ if (rawData == null) {
180
+ throw runtime.newArgumentError("\"raw\" value not defined "
181
+ + "for encoded String");
182
+ }
183
+ RubyArray ary = Utils.ensureArray(rawData);
184
+ byte[] bytes = new byte[ary.getLength()];
185
+ for (int i = 0, t = ary.getLength(); i < t; i++) {
186
+ IRubyObject element = ary.eltInternal(i);
187
+ if (element instanceof RubyFixnum) {
188
+ bytes[i] = (byte)RubyNumeric.fix2long(element);
189
+ } else {
190
+ throw runtime.newTypeError(element, runtime.getFixnum());
191
+ }
192
+ }
193
+ return runtime.newString(new ByteList(bytes, false));
194
+ }
195
+ }
196
+
197
+ public static class RbTrue {
198
+ @JRubyMethod(rest=true)
199
+ public static IRubyObject to_json(ThreadContext context,
200
+ IRubyObject vSelf, IRubyObject[] args) {
201
+ return Generator.generateJson(context, (RubyBoolean)vSelf,
202
+ Generator.TRUE_HANDLER, args);
203
+ }
204
+ }
205
+
206
+ public static class RbFalse {
207
+ @JRubyMethod(rest=true)
208
+ public static IRubyObject to_json(ThreadContext context,
209
+ IRubyObject vSelf, IRubyObject[] args) {
210
+ return Generator.generateJson(context, (RubyBoolean)vSelf,
211
+ Generator.FALSE_HANDLER, args);
212
+ }
213
+ }
214
+
215
+ public static class RbNil {
216
+ @JRubyMethod(rest=true)
217
+ public static IRubyObject to_json(ThreadContext context,
218
+ IRubyObject vSelf, IRubyObject[] args) {
219
+ return Generator.generateJson(context, vSelf,
220
+ Generator.NIL_HANDLER, args);
221
+ }
222
+ }
223
+
224
+ public static class RbObject {
225
+ @JRubyMethod(rest=true)
226
+ public static IRubyObject to_json(ThreadContext context,
227
+ IRubyObject self, IRubyObject[] args) {
228
+ return RbString.to_json(context, self.asString(), args);
229
+ }
230
+ }
231
+ }
@@ -0,0 +1,42 @@
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::Generator</code> module.
19
+ * @author mernen
20
+ */
21
+ public class GeneratorService 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
+ RubyModule generatorModule = jsonExtModule.defineModuleUnder("Generator");
29
+
30
+ RubyClass stateClass =
31
+ generatorModule.defineClassUnder("State", runtime.getObject(),
32
+ GeneratorState.ALLOCATOR);
33
+ stateClass.defineAnnotatedMethods(GeneratorState.class);
34
+ info.generatorStateClass = stateClass;
35
+
36
+ RubyModule generatorMethods =
37
+ generatorModule.defineModuleUnder("GeneratorMethods");
38
+ GeneratorMethods.populate(info, generatorMethods);
39
+
40
+ return true;
41
+ }
42
+ }
@@ -0,0 +1,473 @@
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.RubyBoolean;
11
+ import org.jruby.RubyClass;
12
+ import org.jruby.RubyHash;
13
+ import org.jruby.RubyInteger;
14
+ import org.jruby.RubyNumeric;
15
+ import org.jruby.RubyObject;
16
+ import org.jruby.RubyString;
17
+ import org.jruby.anno.JRubyMethod;
18
+ import org.jruby.runtime.Block;
19
+ import org.jruby.runtime.ObjectAllocator;
20
+ import org.jruby.runtime.ThreadContext;
21
+ import org.jruby.runtime.Visibility;
22
+ import org.jruby.runtime.builtin.IRubyObject;
23
+ import org.jruby.util.ByteList;
24
+
25
+ /**
26
+ * The <code>JSON::Ext::Generator::State</code> class.
27
+ *
28
+ * <p>This class is used to create State instances, that are use to hold data
29
+ * while generating a JSON text from a a Ruby data structure.
30
+ *
31
+ * @author mernen
32
+ */
33
+ public class GeneratorState extends RubyObject {
34
+ /**
35
+ * The indenting unit string. Will be repeated several times for larger
36
+ * indenting levels.
37
+ */
38
+ private ByteList indent = ByteList.EMPTY_BYTELIST;
39
+ /**
40
+ * The spacing to be added after a semicolon on a JSON object.
41
+ * @see #spaceBefore
42
+ */
43
+ private ByteList space = ByteList.EMPTY_BYTELIST;
44
+ /**
45
+ * The spacing to be added before a semicolon on a JSON object.
46
+ * @see #space
47
+ */
48
+ private ByteList spaceBefore = ByteList.EMPTY_BYTELIST;
49
+ /**
50
+ * Any suffix to be added after the comma for each element on a JSON object.
51
+ * It is assumed to be a newline, if set.
52
+ */
53
+ private ByteList objectNl = ByteList.EMPTY_BYTELIST;
54
+ /**
55
+ * Any suffix to be added after the comma for each element on a JSON Array.
56
+ * It is assumed to be a newline, if set.
57
+ */
58
+ private ByteList arrayNl = ByteList.EMPTY_BYTELIST;
59
+
60
+ /**
61
+ * The maximum level of nesting of structures allowed.
62
+ * <code>0</code> means disabled.
63
+ */
64
+ private int maxNesting = DEFAULT_MAX_NESTING;
65
+ static final int DEFAULT_MAX_NESTING = 19;
66
+ /**
67
+ * Whether special float values (<code>NaN</code>, <code>Infinity</code>,
68
+ * <code>-Infinity</code>) are accepted.
69
+ * If set to <code>false</code>, an exception will be thrown upon
70
+ * encountering one.
71
+ */
72
+ private boolean allowNaN = DEFAULT_ALLOW_NAN;
73
+ static final boolean DEFAULT_ALLOW_NAN = false;
74
+ /**
75
+ * XXX
76
+ */
77
+ private boolean asciiOnly = DEFAULT_ASCII_ONLY;
78
+ static final boolean DEFAULT_ASCII_ONLY = false;
79
+
80
+ /**
81
+ * The current depth (inside a #to_json call)
82
+ */
83
+ private int depth = 0;
84
+
85
+ static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
86
+ public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
87
+ return new GeneratorState(runtime, klazz);
88
+ }
89
+ };
90
+
91
+ public GeneratorState(Ruby runtime, RubyClass metaClass) {
92
+ super(runtime, metaClass);
93
+ }
94
+
95
+ /**
96
+ * <code>State.from_state(opts)</code>
97
+ *
98
+ * <p>Creates a State object from <code>opts</code>, which ought to be
99
+ * {@link RubyHash Hash} to create a new <code>State</code> instance
100
+ * configured by <codes>opts</code>, something else to create an
101
+ * unconfigured instance. If <code>opts</code> is a <code>State</code>
102
+ * object, it is just returned.
103
+ * @param clazzParam The receiver of the method call
104
+ * ({@link RubyClass} <code>State</code>)
105
+ * @param opts The object to use as a base for the new <code>State</code>
106
+ * @param block The block passed to the method
107
+ * @return A <code>GeneratorState</code> as determined above
108
+ */
109
+ @JRubyMethod(meta=true)
110
+ public static IRubyObject from_state(ThreadContext context,
111
+ IRubyObject klass, IRubyObject opts) {
112
+ return fromState(context, opts);
113
+ }
114
+
115
+ static GeneratorState fromState(ThreadContext context, IRubyObject opts) {
116
+ return fromState(context, RuntimeInfo.forRuntime(context.getRuntime()), opts);
117
+ }
118
+
119
+ static GeneratorState fromState(ThreadContext context, RuntimeInfo info,
120
+ IRubyObject opts) {
121
+ RubyClass klass = info.generatorStateClass;
122
+ if (opts != null) {
123
+ // if the given parameter is a Generator::State, return itself
124
+ if (klass.isInstance(opts)) return (GeneratorState)opts;
125
+
126
+ // if the given parameter is a Hash, pass it to the instantiator
127
+ if (context.getRuntime().getHash().isInstance(opts)) {
128
+ return (GeneratorState)klass.newInstance(context,
129
+ new IRubyObject[] {opts}, Block.NULL_BLOCK);
130
+ }
131
+ }
132
+
133
+ // for other values, return the safe prototype
134
+ return (GeneratorState)info.getSafeStatePrototype(context).dup();
135
+ }
136
+
137
+ /**
138
+ * <code>State#initialize(opts = {})</code>
139
+ *
140
+ * Instantiates a new <code>State</code> object, configured by <code>opts</code>.
141
+ *
142
+ * <code>opts</code> can have the following keys:
143
+ *
144
+ * <dl>
145
+ * <dt><code>:indent</code>
146
+ * <dd>a {@link RubyString String} used to indent levels (default: <code>""</code>)
147
+ * <dt><code>:space</code>
148
+ * <dd>a String that is put after a <code>':'</code> or <code>','</code>
149
+ * delimiter (default: <code>""</code>)
150
+ * <dt><code>:space_before</code>
151
+ * <dd>a String that is put before a <code>":"</code> pair delimiter
152
+ * (default: <code>""</code>)
153
+ * <dt><code>:object_nl</code>
154
+ * <dd>a String that is put at the end of a JSON object (default: <code>""</code>)
155
+ * <dt><code>:array_nl</code>
156
+ * <dd>a String that is put at the end of a JSON array (default: <code>""</code>)
157
+ * <dt><code>:allow_nan</code>
158
+ * <dd><code>true</code> if <code>NaN</code>, <code>Infinity</code>, and
159
+ * <code>-Infinity</code> should be generated, otherwise an exception is
160
+ * thrown if these values are encountered.
161
+ * This options defaults to <code>false</code>.
162
+ */
163
+ @JRubyMethod(optional=1, visibility=Visibility.PRIVATE)
164
+ public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
165
+ configure(context, args.length > 0 ? args[0] : null);
166
+ return this;
167
+ }
168
+
169
+ @JRubyMethod
170
+ public IRubyObject initialize_copy(ThreadContext context, IRubyObject vOrig) {
171
+ Ruby runtime = context.getRuntime();
172
+ if (!(vOrig instanceof GeneratorState)) {
173
+ throw runtime.newTypeError(vOrig, getType());
174
+ }
175
+ GeneratorState orig = (GeneratorState)vOrig;
176
+ this.indent = orig.indent;
177
+ this.space = orig.space;
178
+ this.spaceBefore = orig.spaceBefore;
179
+ this.objectNl = orig.objectNl;
180
+ this.arrayNl = orig.arrayNl;
181
+ this.maxNesting = orig.maxNesting;
182
+ this.allowNaN = orig.allowNaN;
183
+ this.asciiOnly = orig.asciiOnly;
184
+ this.depth = orig.depth;
185
+ return this;
186
+ }
187
+
188
+ /**
189
+ * XXX
190
+ */
191
+ @JRubyMethod
192
+ public IRubyObject generate(ThreadContext context, IRubyObject obj) {
193
+ RubyString result = Generator.generateJson(context, obj, this);
194
+ if (!objectOrArrayLiteral(result)) {
195
+ throw Utils.newException(context, Utils.M_GENERATOR_ERROR,
196
+ "only generation of JSON objects or arrays allowed");
197
+ }
198
+ return result;
199
+ }
200
+
201
+ /**
202
+ * Ensures the given string is in the form "[...]" or "{...}", being
203
+ * possibly surrounded by white space.
204
+ * The string's encoding must be ASCII-compatible.
205
+ * @param value
206
+ * @return
207
+ */
208
+ private static boolean objectOrArrayLiteral(RubyString value) {
209
+ ByteList bl = value.getByteList();
210
+ int len = bl.length();
211
+
212
+ for (int pos = 0; pos < len - 1; pos++) {
213
+ int b = bl.get(pos);
214
+ if (Character.isWhitespace(b)) continue;
215
+
216
+ // match the opening brace
217
+ switch (b) {
218
+ case '[':
219
+ return matchClosingBrace(bl, pos, len, ']');
220
+ case '{':
221
+ return matchClosingBrace(bl, pos, len, '}');
222
+ default:
223
+ return false;
224
+ }
225
+ }
226
+ return false;
227
+ }
228
+
229
+ private static boolean matchClosingBrace(ByteList bl, int pos, int len,
230
+ int brace) {
231
+ for (int endPos = len - 1; endPos > pos; endPos--) {
232
+ int b = bl.get(endPos);
233
+ if (Character.isWhitespace(b)) continue;
234
+ return b == brace;
235
+ }
236
+ return false;
237
+ }
238
+
239
+ @JRubyMethod(name="[]", required=1)
240
+ public IRubyObject op_aref(ThreadContext context, IRubyObject vName) {
241
+ String name = vName.asJavaString();
242
+ if (getMetaClass().isMethodBound(name, true)) {
243
+ return send(context, vName, Block.NULL_BLOCK);
244
+ }
245
+ return context.getRuntime().getNil();
246
+ }
247
+
248
+ public ByteList getIndent() {
249
+ return indent;
250
+ }
251
+
252
+ @JRubyMethod(name="indent")
253
+ public RubyString indent_get(ThreadContext context) {
254
+ return context.getRuntime().newString(indent);
255
+ }
256
+
257
+ @JRubyMethod(name="indent=")
258
+ public IRubyObject indent_set(ThreadContext context, IRubyObject indent) {
259
+ this.indent = prepareByteList(context, indent);
260
+ return indent;
261
+ }
262
+
263
+ public ByteList getSpace() {
264
+ return space;
265
+ }
266
+
267
+ @JRubyMethod(name="space")
268
+ public RubyString space_get(ThreadContext context) {
269
+ return context.getRuntime().newString(space);
270
+ }
271
+
272
+ @JRubyMethod(name="space=")
273
+ public IRubyObject space_set(ThreadContext context, IRubyObject space) {
274
+ this.space = prepareByteList(context, space);
275
+ return space;
276
+ }
277
+
278
+ public ByteList getSpaceBefore() {
279
+ return spaceBefore;
280
+ }
281
+
282
+ @JRubyMethod(name="space_before")
283
+ public RubyString space_before_get(ThreadContext context) {
284
+ return context.getRuntime().newString(spaceBefore);
285
+ }
286
+
287
+ @JRubyMethod(name="space_before=")
288
+ public IRubyObject space_before_set(ThreadContext context,
289
+ IRubyObject spaceBefore) {
290
+ this.spaceBefore = prepareByteList(context, spaceBefore);
291
+ return spaceBefore;
292
+ }
293
+
294
+ public ByteList getObjectNl() {
295
+ return objectNl;
296
+ }
297
+
298
+ @JRubyMethod(name="object_nl")
299
+ public RubyString object_nl_get(ThreadContext context) {
300
+ return context.getRuntime().newString(objectNl);
301
+ }
302
+
303
+ @JRubyMethod(name="object_nl=")
304
+ public IRubyObject object_nl_set(ThreadContext context,
305
+ IRubyObject objectNl) {
306
+ this.objectNl = prepareByteList(context, objectNl);
307
+ return objectNl;
308
+ }
309
+
310
+ public ByteList getArrayNl() {
311
+ return arrayNl;
312
+ }
313
+
314
+ @JRubyMethod(name="array_nl")
315
+ public RubyString array_nl_get(ThreadContext context) {
316
+ return context.getRuntime().newString(arrayNl);
317
+ }
318
+
319
+ @JRubyMethod(name="array_nl=")
320
+ public IRubyObject array_nl_set(ThreadContext context,
321
+ IRubyObject arrayNl) {
322
+ this.arrayNl = prepareByteList(context, arrayNl);
323
+ return arrayNl;
324
+ }
325
+
326
+ @JRubyMethod(name="check_circular?")
327
+ public RubyBoolean check_circular_p(ThreadContext context) {
328
+ return context.getRuntime().newBoolean(maxNesting != 0);
329
+ }
330
+
331
+ /**
332
+ * Returns the maximum level of nesting configured for this state.
333
+ */
334
+ public int getMaxNesting() {
335
+ return maxNesting;
336
+ }
337
+
338
+ @JRubyMethod(name="max_nesting")
339
+ public RubyInteger max_nesting_get(ThreadContext context) {
340
+ return context.getRuntime().newFixnum(maxNesting);
341
+ }
342
+
343
+ @JRubyMethod(name="max_nesting=")
344
+ public IRubyObject max_nesting_set(IRubyObject max_nesting) {
345
+ maxNesting = RubyNumeric.fix2int(max_nesting);
346
+ return max_nesting;
347
+ }
348
+
349
+ public boolean allowNaN() {
350
+ return allowNaN;
351
+ }
352
+
353
+ @JRubyMethod(name="allow_nan?")
354
+ public RubyBoolean allow_nan_p(ThreadContext context) {
355
+ return context.getRuntime().newBoolean(allowNaN);
356
+ }
357
+
358
+ public boolean asciiOnly() {
359
+ return asciiOnly;
360
+ }
361
+
362
+ @JRubyMethod(name="ascii_only?")
363
+ public RubyBoolean ascii_only_p(ThreadContext context) {
364
+ return context.getRuntime().newBoolean(asciiOnly);
365
+ }
366
+
367
+ public int getDepth() {
368
+ return depth;
369
+ }
370
+
371
+ @JRubyMethod(name="depth")
372
+ public RubyInteger depth_get(ThreadContext context) {
373
+ return context.getRuntime().newFixnum(depth);
374
+ }
375
+
376
+ @JRubyMethod(name="depth=")
377
+ public IRubyObject depth_set(IRubyObject vDepth) {
378
+ depth = RubyNumeric.fix2int(vDepth);
379
+ return vDepth;
380
+ }
381
+
382
+ private ByteList prepareByteList(ThreadContext context, IRubyObject value) {
383
+ RubyString str = value.convertToString();
384
+ RuntimeInfo info = RuntimeInfo.forRuntime(context.getRuntime());
385
+ if (info.encodingsSupported() && str.encoding(context) != info.utf8) {
386
+ str = (RubyString)str.encode(context, info.utf8);
387
+ }
388
+ return str.getByteList().dup();
389
+ }
390
+
391
+ /**
392
+ * <code>State#configure(opts)</code>
393
+ *
394
+ * <p>Configures this State instance with the {@link RubyHash Hash}
395
+ * <code>opts</code>, and returns itself.
396
+ * @param vOpts The options hash
397
+ * @return The receiver
398
+ */
399
+ @JRubyMethod
400
+ public IRubyObject configure(ThreadContext context, IRubyObject vOpts) {
401
+ OptionsReader opts = new OptionsReader(context, vOpts);
402
+
403
+ ByteList indent = opts.getString("indent");
404
+ if (indent != null) this.indent = indent;
405
+
406
+ ByteList space = opts.getString("space");
407
+ if (space != null) this.space = space;
408
+
409
+ ByteList spaceBefore = opts.getString("space_before");
410
+ if (spaceBefore != null) this.spaceBefore = spaceBefore;
411
+
412
+ ByteList arrayNl = opts.getString("array_nl");
413
+ if (arrayNl != null) this.arrayNl = arrayNl;
414
+
415
+ ByteList objectNl = opts.getString("object_nl");
416
+ if (objectNl != null) this.objectNl = objectNl;
417
+
418
+ maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING);
419
+ allowNaN = opts.getBool("allow_nan", DEFAULT_ALLOW_NAN);
420
+ asciiOnly = opts.getBool("ascii_only", DEFAULT_ASCII_ONLY);
421
+
422
+ depth = opts.getInt("depth", 0);
423
+
424
+ return this;
425
+ }
426
+
427
+ /**
428
+ * <code>State#to_h()</code>
429
+ *
430
+ * <p>Returns the configuration instance variables as a hash, that can be
431
+ * passed to the configure method.
432
+ * @return
433
+ */
434
+ @JRubyMethod
435
+ public RubyHash to_h(ThreadContext context) {
436
+ Ruby runtime = context.getRuntime();
437
+ RubyHash result = RubyHash.newHash(runtime);
438
+
439
+ result.op_aset(context, runtime.newSymbol("indent"), indent_get(context));
440
+ result.op_aset(context, runtime.newSymbol("space"), space_get(context));
441
+ result.op_aset(context, runtime.newSymbol("space_before"), space_before_get(context));
442
+ result.op_aset(context, runtime.newSymbol("object_nl"), object_nl_get(context));
443
+ result.op_aset(context, runtime.newSymbol("array_nl"), array_nl_get(context));
444
+ result.op_aset(context, runtime.newSymbol("allow_nan"), allow_nan_p(context));
445
+ result.op_aset(context, runtime.newSymbol("ascii_only"), ascii_only_p(context));
446
+ result.op_aset(context, runtime.newSymbol("max_nesting"), max_nesting_get(context));
447
+ result.op_aset(context, runtime.newSymbol("depth"), depth_get(context));
448
+ return result;
449
+ }
450
+
451
+ public int increaseDepth() {
452
+ depth++;
453
+ checkMaxNesting();
454
+ return depth;
455
+ }
456
+
457
+ public int decreaseDepth() {
458
+ return --depth;
459
+ }
460
+
461
+ /**
462
+ * Checks if the current depth is allowed as per this state's options.
463
+ * @param context
464
+ * @param depth The corrent depth
465
+ */
466
+ private void checkMaxNesting() {
467
+ if (maxNesting != 0 && depth > maxNesting) {
468
+ depth--;
469
+ throw Utils.newException(getRuntime().getCurrentContext(),
470
+ Utils.M_NESTING_ERROR, "nesting of " + depth + " is too deep");
471
+ }
472
+ }
473
+ }