2 * This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
4 * Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
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;
26 * The <code>JSON::Ext::Generator::State</code> class.
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.
33 public class GeneratorState extends RubyObject {
35 * The indenting unit string. Will be repeated several times for larger
38 private ByteList indent = ByteList.EMPTY_BYTELIST;
40 * The spacing to be added after a semicolon on a JSON object.
43 private ByteList space = ByteList.EMPTY_BYTELIST;
45 * The spacing to be added before a semicolon on a JSON object.
48 private ByteList spaceBefore = ByteList.EMPTY_BYTELIST;
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.
53 private ByteList objectNl = ByteList.EMPTY_BYTELIST;
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.
58 private ByteList arrayNl = ByteList.EMPTY_BYTELIST;
61 * The maximum level of nesting of structures allowed.
62 * <code>0</code> means disabled.
64 private int maxNesting = DEFAULT_MAX_NESTING;
65 static final int DEFAULT_MAX_NESTING = 19;
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
72 private boolean allowNaN = DEFAULT_ALLOW_NAN;
73 static final boolean DEFAULT_ALLOW_NAN = false;
75 * If set to <code>true</code> all JSON documents generated do not contain
76 * any other characters than ASCII characters.
78 private boolean asciiOnly = DEFAULT_ASCII_ONLY;
79 static final boolean DEFAULT_ASCII_ONLY = false;
81 * If set to <code>true</code> all JSON values generated might not be
82 * RFC-conform JSON documents.
84 private boolean quirksMode = DEFAULT_QUIRKS_MODE;
85 static final boolean DEFAULT_QUIRKS_MODE = false;
88 * The current depth (inside a #to_json call)
90 private int depth = 0;
92 static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
93 public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
94 return new GeneratorState(runtime, klazz);
98 public GeneratorState(Ruby runtime, RubyClass metaClass) {
99 super(runtime, metaClass);
103 * <code>State.from_state(opts)</code>
105 * <p>Creates a State object from <code>opts</code>, which ought to be
106 * {@link RubyHash Hash} to create a new <code>State</code> instance
107 * configured by <codes>opts</code>, something else to create an
108 * unconfigured instance. If <code>opts</code> is a <code>State</code>
109 * object, it is just returned.
110 * @param clazzParam The receiver of the method call
111 * ({@link RubyClass} <code>State</code>)
112 * @param opts The object to use as a base for the new <code>State</code>
113 * @param block The block passed to the method
114 * @return A <code>GeneratorState</code> as determined above
116 @JRubyMethod(meta=true)
117 public static IRubyObject from_state(ThreadContext context,
118 IRubyObject klass, IRubyObject opts) {
119 return fromState(context, opts);
122 static GeneratorState fromState(ThreadContext context, IRubyObject opts) {
123 return fromState(context, RuntimeInfo.forRuntime(context.getRuntime()), opts);
126 static GeneratorState fromState(ThreadContext context, RuntimeInfo info,
128 RubyClass klass = info.generatorStateClass.get();
130 // if the given parameter is a Generator::State, return itself
131 if (klass.isInstance(opts)) return (GeneratorState)opts;
133 // if the given parameter is a Hash, pass it to the instantiator
134 if (context.getRuntime().getHash().isInstance(opts)) {
135 return (GeneratorState)klass.newInstance(context,
136 new IRubyObject[] {opts}, Block.NULL_BLOCK);
140 // for other values, return the safe prototype
141 return (GeneratorState)info.getSafeStatePrototype(context).dup();
145 * <code>State#initialize(opts = {})</code>
147 * Instantiates a new <code>State</code> object, configured by <code>opts</code>.
149 * <code>opts</code> can have the following keys:
152 * <dt><code>:indent</code>
153 * <dd>a {@link RubyString String} used to indent levels (default: <code>""</code>)
154 * <dt><code>:space</code>
155 * <dd>a String that is put after a <code>':'</code> or <code>','</code>
156 * delimiter (default: <code>""</code>)
157 * <dt><code>:space_before</code>
158 * <dd>a String that is put before a <code>":"</code> pair delimiter
159 * (default: <code>""</code>)
160 * <dt><code>:object_nl</code>
161 * <dd>a String that is put at the end of a JSON object (default: <code>""</code>)
162 * <dt><code>:array_nl</code>
163 * <dd>a String that is put at the end of a JSON array (default: <code>""</code>)
164 * <dt><code>:allow_nan</code>
165 * <dd><code>true</code> if <code>NaN</code>, <code>Infinity</code>, and
166 * <code>-Infinity</code> should be generated, otherwise an exception is
167 * thrown if these values are encountered.
168 * This options defaults to <code>false</code>.
170 @JRubyMethod(optional=1, visibility=Visibility.PRIVATE)
171 public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
172 configure(context, args.length > 0 ? args[0] : null);
177 public IRubyObject initialize_copy(ThreadContext context, IRubyObject vOrig) {
178 Ruby runtime = context.getRuntime();
179 if (!(vOrig instanceof GeneratorState)) {
180 throw runtime.newTypeError(vOrig, getType());
182 GeneratorState orig = (GeneratorState)vOrig;
183 this.indent = orig.indent;
184 this.space = orig.space;
185 this.spaceBefore = orig.spaceBefore;
186 this.objectNl = orig.objectNl;
187 this.arrayNl = orig.arrayNl;
188 this.maxNesting = orig.maxNesting;
189 this.allowNaN = orig.allowNaN;
190 this.asciiOnly = orig.asciiOnly;
191 this.quirksMode = orig.quirksMode;
192 this.depth = orig.depth;
197 * Generates a valid JSON document from object <code>obj</code> and returns
198 * the result. If no valid JSON document can be created this method raises
199 * a GeneratorError exception.
202 public IRubyObject generate(ThreadContext context, IRubyObject obj) {
203 RubyString result = Generator.generateJson(context, obj, this);
204 if (!quirksMode && !objectOrArrayLiteral(result)) {
205 throw Utils.newException(context, Utils.M_GENERATOR_ERROR,
206 "only generation of JSON objects or arrays allowed");
212 * Ensures the given string is in the form "[...]" or "{...}", being
213 * possibly surrounded by white space.
214 * The string's encoding must be ASCII-compatible.
218 private static boolean objectOrArrayLiteral(RubyString value) {
219 ByteList bl = value.getByteList();
220 int len = bl.length();
222 for (int pos = 0; pos < len - 1; pos++) {
224 if (Character.isWhitespace(b)) continue;
226 // match the opening brace
229 return matchClosingBrace(bl, pos, len, ']');
231 return matchClosingBrace(bl, pos, len, '}');
239 private static boolean matchClosingBrace(ByteList bl, int pos, int len,
241 for (int endPos = len - 1; endPos > pos; endPos--) {
242 int b = bl.get(endPos);
243 if (Character.isWhitespace(b)) continue;
249 @JRubyMethod(name="[]", required=1)
250 public IRubyObject op_aref(ThreadContext context, IRubyObject vName) {
251 String name = vName.asJavaString();
252 if (getMetaClass().isMethodBound(name, true)) {
253 return send(context, vName, Block.NULL_BLOCK);
255 return context.getRuntime().getNil();
258 public ByteList getIndent() {
262 @JRubyMethod(name="indent")
263 public RubyString indent_get(ThreadContext context) {
264 return context.getRuntime().newString(indent);
267 @JRubyMethod(name="indent=")
268 public IRubyObject indent_set(ThreadContext context, IRubyObject indent) {
269 this.indent = prepareByteList(context, indent);
273 public ByteList getSpace() {
277 @JRubyMethod(name="space")
278 public RubyString space_get(ThreadContext context) {
279 return context.getRuntime().newString(space);
282 @JRubyMethod(name="space=")
283 public IRubyObject space_set(ThreadContext context, IRubyObject space) {
284 this.space = prepareByteList(context, space);
288 public ByteList getSpaceBefore() {
292 @JRubyMethod(name="space_before")
293 public RubyString space_before_get(ThreadContext context) {
294 return context.getRuntime().newString(spaceBefore);
297 @JRubyMethod(name="space_before=")
298 public IRubyObject space_before_set(ThreadContext context,
299 IRubyObject spaceBefore) {
300 this.spaceBefore = prepareByteList(context, spaceBefore);
304 public ByteList getObjectNl() {
308 @JRubyMethod(name="object_nl")
309 public RubyString object_nl_get(ThreadContext context) {
310 return context.getRuntime().newString(objectNl);
313 @JRubyMethod(name="object_nl=")
314 public IRubyObject object_nl_set(ThreadContext context,
315 IRubyObject objectNl) {
316 this.objectNl = prepareByteList(context, objectNl);
320 public ByteList getArrayNl() {
324 @JRubyMethod(name="array_nl")
325 public RubyString array_nl_get(ThreadContext context) {
326 return context.getRuntime().newString(arrayNl);
329 @JRubyMethod(name="array_nl=")
330 public IRubyObject array_nl_set(ThreadContext context,
331 IRubyObject arrayNl) {
332 this.arrayNl = prepareByteList(context, arrayNl);
336 @JRubyMethod(name="check_circular?")
337 public RubyBoolean check_circular_p(ThreadContext context) {
338 return context.getRuntime().newBoolean(maxNesting != 0);
342 * Returns the maximum level of nesting configured for this state.
344 public int getMaxNesting() {
348 @JRubyMethod(name="max_nesting")
349 public RubyInteger max_nesting_get(ThreadContext context) {
350 return context.getRuntime().newFixnum(maxNesting);
353 @JRubyMethod(name="max_nesting=")
354 public IRubyObject max_nesting_set(IRubyObject max_nesting) {
355 maxNesting = RubyNumeric.fix2int(max_nesting);
359 public boolean allowNaN() {
363 @JRubyMethod(name="allow_nan?")
364 public RubyBoolean allow_nan_p(ThreadContext context) {
365 return context.getRuntime().newBoolean(allowNaN);
368 public boolean asciiOnly() {
372 @JRubyMethod(name="ascii_only?")
373 public RubyBoolean ascii_only_p(ThreadContext context) {
374 return context.getRuntime().newBoolean(asciiOnly);
377 @JRubyMethod(name="quirks_mode")
378 public RubyBoolean quirks_mode_get(ThreadContext context) {
379 return context.getRuntime().newBoolean(quirksMode);
382 @JRubyMethod(name="quirks_mode=")
383 public IRubyObject quirks_mode_set(IRubyObject quirks_mode) {
384 quirksMode = quirks_mode.isTrue();
385 return quirks_mode.getRuntime().newBoolean(quirksMode);
388 @JRubyMethod(name="quirks_mode?")
389 public RubyBoolean quirks_mode_p(ThreadContext context) {
390 return context.getRuntime().newBoolean(quirksMode);
393 public int getDepth() {
397 @JRubyMethod(name="depth")
398 public RubyInteger depth_get(ThreadContext context) {
399 return context.getRuntime().newFixnum(depth);
402 @JRubyMethod(name="depth=")
403 public IRubyObject depth_set(IRubyObject vDepth) {
404 depth = RubyNumeric.fix2int(vDepth);
408 private ByteList prepareByteList(ThreadContext context, IRubyObject value) {
409 RubyString str = value.convertToString();
410 RuntimeInfo info = RuntimeInfo.forRuntime(context.getRuntime());
411 if (info.encodingsSupported() && str.encoding(context) != info.utf8.get()) {
412 str = (RubyString)str.encode(context, info.utf8.get());
414 return str.getByteList().dup();
418 * <code>State#configure(opts)</code>
420 * <p>Configures this State instance with the {@link RubyHash Hash}
421 * <code>opts</code>, and returns itself.
422 * @param vOpts The options hash
423 * @return The receiver
426 public IRubyObject configure(ThreadContext context, IRubyObject vOpts) {
427 OptionsReader opts = new OptionsReader(context, vOpts);
429 ByteList indent = opts.getString("indent");
430 if (indent != null) this.indent = indent;
432 ByteList space = opts.getString("space");
433 if (space != null) this.space = space;
435 ByteList spaceBefore = opts.getString("space_before");
436 if (spaceBefore != null) this.spaceBefore = spaceBefore;
438 ByteList arrayNl = opts.getString("array_nl");
439 if (arrayNl != null) this.arrayNl = arrayNl;
441 ByteList objectNl = opts.getString("object_nl");
442 if (objectNl != null) this.objectNl = objectNl;
444 maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING);
445 allowNaN = opts.getBool("allow_nan", DEFAULT_ALLOW_NAN);
446 asciiOnly = opts.getBool("ascii_only", DEFAULT_ASCII_ONLY);
447 quirksMode = opts.getBool("quirks_mode", DEFAULT_QUIRKS_MODE);
449 depth = opts.getInt("depth", 0);
455 * <code>State#to_h()</code>
457 * <p>Returns the configuration instance variables as a hash, that can be
458 * passed to the configure method.
462 public RubyHash to_h(ThreadContext context) {
463 Ruby runtime = context.getRuntime();
464 RubyHash result = RubyHash.newHash(runtime);
466 result.op_aset(context, runtime.newSymbol("indent"), indent_get(context));
467 result.op_aset(context, runtime.newSymbol("space"), space_get(context));
468 result.op_aset(context, runtime.newSymbol("space_before"), space_before_get(context));
469 result.op_aset(context, runtime.newSymbol("object_nl"), object_nl_get(context));
470 result.op_aset(context, runtime.newSymbol("array_nl"), array_nl_get(context));
471 result.op_aset(context, runtime.newSymbol("allow_nan"), allow_nan_p(context));
472 result.op_aset(context, runtime.newSymbol("ascii_only"), ascii_only_p(context));
473 result.op_aset(context, runtime.newSymbol("quirks_mode"), quirks_mode_p(context));
474 result.op_aset(context, runtime.newSymbol("max_nesting"), max_nesting_get(context));
475 result.op_aset(context, runtime.newSymbol("depth"), depth_get(context));
479 public int increaseDepth() {
485 public int decreaseDepth() {
490 * Checks if the current depth is allowed as per this state's options.
492 * @param depth The corrent depth
494 private void checkMaxNesting() {
495 if (maxNesting != 0 && depth > maxNesting) {
497 throw Utils.newException(getRuntime().getCurrentContext(),
498 Utils.M_NESTING_ERROR, "nesting of " + depth + " is too deep");