78524a1c2114a62f0924d2dc71006d376c6fa1a8
[packages/precise/mcollective.git] / lib / mcollective / vendor / json / java / src / json / ext / GeneratorState.java
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      * If set to <code>true</code> all JSON documents generated do not contain
76      * any other characters than ASCII characters.
77      */
78     private boolean asciiOnly = DEFAULT_ASCII_ONLY;
79     static final boolean DEFAULT_ASCII_ONLY = false;
80     /**
81      * If set to <code>true</code> all JSON values generated might not be
82      * RFC-conform JSON documents.
83      */
84     private boolean quirksMode = DEFAULT_QUIRKS_MODE;
85     static final boolean DEFAULT_QUIRKS_MODE = false;
86
87     /**
88      * The current depth (inside a #to_json call)
89      */
90     private int depth = 0;
91
92     static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
93         public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
94             return new GeneratorState(runtime, klazz);
95         }
96     };
97
98     public GeneratorState(Ruby runtime, RubyClass metaClass) {
99         super(runtime, metaClass);
100     }
101
102     /**
103      * <code>State.from_state(opts)</code>
104      *
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
115      */
116     @JRubyMethod(meta=true)
117     public static IRubyObject from_state(ThreadContext context,
118             IRubyObject klass, IRubyObject opts) {
119         return fromState(context, opts);
120     }
121
122     static GeneratorState fromState(ThreadContext context, IRubyObject opts) {
123         return fromState(context, RuntimeInfo.forRuntime(context.getRuntime()), opts);
124     }
125
126     static GeneratorState fromState(ThreadContext context, RuntimeInfo info,
127                                     IRubyObject opts) {
128         RubyClass klass = info.generatorStateClass.get();
129         if (opts != null) {
130             // if the given parameter is a Generator::State, return itself
131             if (klass.isInstance(opts)) return (GeneratorState)opts;
132
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);
137             }
138         }
139
140         // for other values, return the safe prototype
141         return (GeneratorState)info.getSafeStatePrototype(context).dup();
142     }
143
144     /**
145      * <code>State#initialize(opts = {})</code>
146      *
147      * Instantiates a new <code>State</code> object, configured by <code>opts</code>.
148      *
149      * <code>opts</code> can have the following keys:
150      *
151      * <dl>
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>.
169      */
170     @JRubyMethod(optional=1, visibility=Visibility.PRIVATE)
171     public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
172         configure(context, args.length > 0 ? args[0] : null);
173         return this;
174     }
175
176     @JRubyMethod
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());
181         }
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;
193         return this;
194     }
195
196     /**
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.
200      */
201     @JRubyMethod
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");
207         }
208         return result;
209     }
210
211     /**
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.
215      * @param value
216      * @return
217      */
218     private static boolean objectOrArrayLiteral(RubyString value) {
219         ByteList bl = value.getByteList();
220         int len = bl.length();
221
222         for (int pos = 0; pos < len - 1; pos++) {
223             int b = bl.get(pos);
224             if (Character.isWhitespace(b)) continue;
225
226             // match the opening brace
227             switch (b) {
228             case '[':
229                 return matchClosingBrace(bl, pos, len, ']');
230             case '{':
231                 return matchClosingBrace(bl, pos, len, '}');
232             default:
233                 return false;
234             }
235         }
236         return false;
237     }
238
239     private static boolean matchClosingBrace(ByteList bl, int pos, int len,
240                                              int brace) {
241         for (int endPos = len - 1; endPos > pos; endPos--) {
242             int b = bl.get(endPos);
243             if (Character.isWhitespace(b)) continue;
244             return b == brace;
245         }
246         return false;
247     }
248
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);
254         }
255         return context.getRuntime().getNil();
256     }
257
258     public ByteList getIndent() {
259         return indent;
260     }
261
262     @JRubyMethod(name="indent")
263     public RubyString indent_get(ThreadContext context) {
264         return context.getRuntime().newString(indent);
265     }
266
267     @JRubyMethod(name="indent=")
268     public IRubyObject indent_set(ThreadContext context, IRubyObject indent) {
269         this.indent = prepareByteList(context, indent);
270         return indent;
271     }
272
273     public ByteList getSpace() {
274         return space;
275     }
276
277     @JRubyMethod(name="space")
278     public RubyString space_get(ThreadContext context) {
279         return context.getRuntime().newString(space);
280     }
281
282     @JRubyMethod(name="space=")
283     public IRubyObject space_set(ThreadContext context, IRubyObject space) {
284         this.space = prepareByteList(context, space);
285         return space;
286     }
287
288     public ByteList getSpaceBefore() {
289         return spaceBefore;
290     }
291
292     @JRubyMethod(name="space_before")
293     public RubyString space_before_get(ThreadContext context) {
294         return context.getRuntime().newString(spaceBefore);
295     }
296
297     @JRubyMethod(name="space_before=")
298     public IRubyObject space_before_set(ThreadContext context,
299                                         IRubyObject spaceBefore) {
300         this.spaceBefore = prepareByteList(context, spaceBefore);
301         return spaceBefore;
302     }
303
304     public ByteList getObjectNl() {
305         return objectNl;
306     }
307
308     @JRubyMethod(name="object_nl")
309     public RubyString object_nl_get(ThreadContext context) {
310         return context.getRuntime().newString(objectNl);
311     }
312
313     @JRubyMethod(name="object_nl=")
314     public IRubyObject object_nl_set(ThreadContext context,
315                                      IRubyObject objectNl) {
316         this.objectNl = prepareByteList(context, objectNl);
317         return objectNl;
318     }
319
320     public ByteList getArrayNl() {
321         return arrayNl;
322     }
323
324     @JRubyMethod(name="array_nl")
325     public RubyString array_nl_get(ThreadContext context) {
326         return context.getRuntime().newString(arrayNl);
327     }
328
329     @JRubyMethod(name="array_nl=")
330     public IRubyObject array_nl_set(ThreadContext context,
331                                     IRubyObject arrayNl) {
332         this.arrayNl = prepareByteList(context, arrayNl);
333         return arrayNl;
334     }
335
336     @JRubyMethod(name="check_circular?")
337     public RubyBoolean check_circular_p(ThreadContext context) {
338         return context.getRuntime().newBoolean(maxNesting != 0);
339     }
340
341     /**
342      * Returns the maximum level of nesting configured for this state.
343      */
344     public int getMaxNesting() {
345         return maxNesting;
346     }
347
348     @JRubyMethod(name="max_nesting")
349     public RubyInteger max_nesting_get(ThreadContext context) {
350         return context.getRuntime().newFixnum(maxNesting);
351     }
352
353     @JRubyMethod(name="max_nesting=")
354     public IRubyObject max_nesting_set(IRubyObject max_nesting) {
355         maxNesting = RubyNumeric.fix2int(max_nesting);
356         return max_nesting;
357     }
358
359     public boolean allowNaN() {
360         return allowNaN;
361     }
362
363     @JRubyMethod(name="allow_nan?")
364     public RubyBoolean allow_nan_p(ThreadContext context) {
365         return context.getRuntime().newBoolean(allowNaN);
366     }
367
368     public boolean asciiOnly() {
369         return asciiOnly;
370     }
371
372     @JRubyMethod(name="ascii_only?")
373     public RubyBoolean ascii_only_p(ThreadContext context) {
374         return context.getRuntime().newBoolean(asciiOnly);
375     }
376
377     @JRubyMethod(name="quirks_mode")
378     public RubyBoolean quirks_mode_get(ThreadContext context) {
379         return context.getRuntime().newBoolean(quirksMode);
380     }
381
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);
386     }
387
388     @JRubyMethod(name="quirks_mode?")
389     public RubyBoolean quirks_mode_p(ThreadContext context) {
390         return context.getRuntime().newBoolean(quirksMode);
391     }
392
393     public int getDepth() {
394         return depth;
395     }
396
397     @JRubyMethod(name="depth")
398     public RubyInteger depth_get(ThreadContext context) {
399         return context.getRuntime().newFixnum(depth);
400     }
401
402     @JRubyMethod(name="depth=")
403     public IRubyObject depth_set(IRubyObject vDepth) {
404         depth = RubyNumeric.fix2int(vDepth);
405         return vDepth;
406     }
407
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());
413         }
414         return str.getByteList().dup();
415     }
416
417     /**
418      * <code>State#configure(opts)</code>
419      *
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
424      */
425     @JRubyMethod
426     public IRubyObject configure(ThreadContext context, IRubyObject vOpts) {
427         OptionsReader opts = new OptionsReader(context, vOpts);
428
429         ByteList indent = opts.getString("indent");
430         if (indent != null) this.indent = indent;
431
432         ByteList space = opts.getString("space");
433         if (space != null) this.space = space;
434
435         ByteList spaceBefore = opts.getString("space_before");
436         if (spaceBefore != null) this.spaceBefore = spaceBefore;
437
438         ByteList arrayNl = opts.getString("array_nl");
439         if (arrayNl != null) this.arrayNl = arrayNl;
440
441         ByteList objectNl = opts.getString("object_nl");
442         if (objectNl != null) this.objectNl = objectNl;
443
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);
448
449         depth = opts.getInt("depth", 0);
450
451         return this;
452     }
453
454     /**
455      * <code>State#to_h()</code>
456      *
457      * <p>Returns the configuration instance variables as a hash, that can be
458      * passed to the configure method.
459      * @return
460      */
461     @JRubyMethod
462     public RubyHash to_h(ThreadContext context) {
463         Ruby runtime = context.getRuntime();
464         RubyHash result = RubyHash.newHash(runtime);
465
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));
476         return result;
477     }
478
479     public int increaseDepth() {
480         depth++;
481         checkMaxNesting();
482         return depth;
483     }
484
485     public int decreaseDepth() {
486         return --depth;
487     }
488
489     /**
490      * Checks if the current depth is allowed as per this state's options.
491      * @param context
492      * @param depth The corrent depth
493      */
494     private void checkMaxNesting() {
495         if (maxNesting != 0 && depth > maxNesting) {
496             depth--;
497             throw Utils.newException(getRuntime().getCurrentContext(),
498                     Utils.M_NESTING_ERROR, "nesting of " + depth + " is too deep");
499         }
500     }
501 }