Permalink
Browse files

Merge JRuby-compatible extension into json proper.

  • Loading branch information...
1 parent 6ebee56 commit 2288d74466463d34a5054ef413e099c850cd832b @headius headius committed Aug 3, 2010
View
57 COPYING-json-jruby
@@ -0,0 +1,57 @@
+JSON-JRuby is copyrighted free software by Daniel Luz <mernen at gmail dot com>,
+and is a derivative work of Florian Frank's json library <flori at ping dot de>.
+You can redistribute it and/or modify it under either the terms of the GPL
+version 2 (see the file GPL), or the conditions below:
+
+ 1. You may make and give away verbatim copies of the source form of the
+ software without restriction, provided that you duplicate all of the
+ original copyright notices and associated disclaimers.
+
+ 2. You may modify your copy of the software in any way, provided that
+ you do at least ONE of the following:
+
+ a) place your modifications in the Public Domain or otherwise
+ make them Freely Available, such as by posting said
+ modifications to Usenet or an equivalent medium, or by allowing
+ the author to include your modifications in the software.
+
+ b) use the modified software only within your corporation or
+ organization.
+
+ c) give non-standard binaries non-standard names, with
+ instructions on where to get the original software distribution.
+
+ d) make other distribution arrangements with the author.
+
+ 3. You may distribute the software in object code or binary form,
+ provided that you do at least ONE of the following:
+
+ a) distribute the binaries and library files of the software,
+ together with instructions (in the manual page or equivalent)
+ on where to get the original distribution.
+
+ b) accompany the distribution with the machine-readable source of
+ the software.
+
+ c) give non-standard binaries non-standard names, with
+ instructions on where to get the original software distribution.
+
+ d) make other distribution arrangements with the author.
+
+ 4. You may modify and include the part of the software into any other
+ software (possibly commercial). But some files in the distribution
+ are not written by the author, so that they are not under these terms.
+
+ For the list of those files and their copying conditions, see the
+ file LEGAL.
+
+ 5. The scripts and library files supplied as input to or produced as
+ output from the software do not automatically fall under the
+ copyright of the software, but belong to whomever generated them,
+ and may be sold commercially, and may be aggregated with this
+ software.
+
+ 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE.
View
33 README-json-jruby.markdown
@@ -0,0 +1,33 @@
+JSON-JRuby
+==========
+
+JSON-JRuby is a port of Florian Frank's native
+[`json` library](http://json.rubyforge.org/) to JRuby.
+It aims to be a perfect drop-in replacement for `json_pure`.
+
+
+Development version
+===================
+
+The latest version is available from the
+[Git repository](http://github.com/mernen/json-jruby/tree):
+
+ git clone git://github.com/mernen/json-jruby.git
+
+
+Compiling
+=========
+
+You'll need JRuby version 1.2 or greater to build JSON-JRuby.
+Its path must be set on the `jruby.dir` property of
+`nbproject/project.properties` (defaults to `../jruby`).
+
+Additionally, you'll need [Ant](http://ant.apache.org/), and
+[Ragel](http://www.cs.queensu.ca/~thurston/ragel/) 6.4 or greater.
+
+Then, from the folder where the sources are located, type:
+
+ ant clean jar
+
+to clean any leftovers from previous builds and generate the `.jar` files.
+To generate a RubyGem, specify the `gem` action rather than `jar`.
View
71 build.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="JSON-JRuby" default="gem" basedir=".">
+ <description>Builds, tests, and runs the project JSON-JRuby.</description>
+ <import file="nbproject/build-impl.xml"/>
+
+ <target name="ragel" description="Generate parser with Ragel.">
+ <exec executable="ragel" failonerror="true">
+ <arg value="-J"/>
+ <arg value="${src.dir}/json/ext/Parser.rl"/>
+ </exec>
+ </target>
+
+ <target name="-pre-compile" depends="ragel">
+ </target>
+
+ <target name="clean-dist">
+ <delete>
+ <file file="${generator.jar}"/>
+ <file file="${parser.jar}"/>
+ <fileset dir="." includes="*.gem"/>
+ </delete>
+ </target>
+
+ <target name="-post-clean" depends="clean-dist,ragel"/>
+
+ <target name="-do-jar-without-manifest">
+ <mkdir dir="${lib.dir}/json/ext"/>
+ <delete>
+ <file file="${generator.jar}"/>
+ <file file="${parser.jar}"/>
+ </delete>
+ <jar destfile="${generator.jar}">
+ <fileset dir="${build.classes.dir}">
+ <include name="json/ext/ByteListTranscoder*.class"/>
+ <include name="json/ext/Generator*.class"/>
+ <include name="json/ext/OptionsReader*.class"/>
+ <include name="json/ext/RuntimeInfo*.class"/>
+ <include name="json/ext/StringEncoder*.class"/>
+ <include name="json/ext/Utils*.class"/>
+ </fileset>
+ </jar>
+ <jar destfile="${parser.jar}">
+ <fileset dir="${build.classes.dir}">
+ <include name="json/ext/ByteListTranscoder*.class"/>
+ <include name="json/ext/OptionsReader*.class"/>
+ <include name="json/ext/Parser*.class"/>
+ <include name="json/ext/RuntimeInfo*.class"/>
+ <include name="json/ext/StringDecoder*.class"/>
+ <include name="json/ext/Utils*.class"/>
+ </fileset>
+ </jar>
+ </target>
+
+ <target name="gem" depends="jar" description="Build a RubyGem.">
+ <exec executable="${jruby.dir}/bin/jruby">
+ <arg value="json-jruby.gemspec"/>
+ </exec>
+ </target>
+
+ <target name="-post-test" depends="ruby-tests" />
+
+ <target name="ruby-tests" depends="jar"
+ description="Perform the json ruby library tests.">
+ <exec executable="${jruby.dir}/bin/jruby" failonerror="true">
+ <arg value="-v"/>
+ <arg value="-I"/>
+ <arg value="${lib.dir}"/>
+ <arg value="tests/runner.rb"/>
+ </exec>
+ </target>
+</project>
View
20 json-jruby.gemspec
@@ -0,0 +1,20 @@
+#! /usr/bin/env jruby
+require "rubygems"
+
+spec = Gem::Specification.new do |s|
+ s.name = "json"
+ s.version = File.read("VERSION").chomp
+ s.summary = "JSON implementation for JRuby"
+ s.description = "A JSON implementation as a JRuby extension."
+ s.author = "Daniel Luz"
+ s.email = "dev+ruby@mernen.com"
+ s.homepage = "http://json-jruby.rubyforge.org/"
+ s.platform = 'java'
+ s.rubyforge_project = "json-jruby"
+
+ s.files = Dir["{docs,lib,tests}/**/*"]
+end
+
+if $0 == __FILE__
+ Gem::Builder.new(spec).build
+end
View
700 nbproject/build-impl.xml
@@ -0,0 +1,700 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+*** GENERATED FROM project.xml - DO NOT EDIT ***
+*** EDIT ../build.xml INSTEAD ***
+
+For the purpose of easier reading the script
+is divided into following sections:
+
+ - initialization
+ - compilation
+ - jar
+ - execution
+ - debugging
+ - javadoc
+ - junit compilation
+ - junit execution
+ - junit debugging
+ - applet
+ - cleanup
+
+ -->
+<project xmlns:j2seproject1="http://www.netbeans.org/ns/j2se-project/1" xmlns:j2seproject3="http://www.netbeans.org/ns/j2se-project/3" xmlns:jaxrpc="http://www.netbeans.org/ns/j2se-project/jax-rpc" basedir=".." default="default" name="JSON-JRuby-impl">
+ <fail message="Please build using Ant 1.7.1 or higher.">
+ <condition>
+ <not>
+ <antversion atleast="1.7.1"/>
+ </not>
+ </condition>
+ </fail>
+ <target depends="test,jar,javadoc" description="Build and test whole project." name="default"/>
+ <!--
+ ======================
+ INITIALIZATION SECTION
+ ======================
+ -->
+ <target name="-pre-init">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="-pre-init" name="-init-private">
+ <property file="nbproject/private/config.properties"/>
+ <property file="nbproject/private/configs/${config}.properties"/>
+ <property file="nbproject/private/private.properties"/>
+ </target>
+ <target depends="-pre-init,-init-private" name="-init-user">
+ <property file="${user.properties.file}"/>
+ <!-- The two properties below are usually overridden -->
+ <!-- by the active platform. Just a fallback. -->
+ <property name="default.javac.source" value="1.4"/>
+ <property name="default.javac.target" value="1.4"/>
+ </target>
+ <target depends="-pre-init,-init-private,-init-user" name="-init-project">
+ <property file="nbproject/configs/${config}.properties"/>
+ <property file="nbproject/project.properties"/>
+ </target>
+ <target depends="-pre-init,-init-private,-init-user,-init-project,-init-macrodef-property" name="-do-init">
+ <available file="${manifest.file}" property="manifest.available"/>
+ <condition property="manifest.available+main.class">
+ <and>
+ <isset property="manifest.available"/>
+ <isset property="main.class"/>
+ <not>
+ <equals arg1="${main.class}" arg2="" trim="true"/>
+ </not>
+ </and>
+ </condition>
+ <condition property="manifest.available+main.class+mkdist.available">
+ <and>
+ <istrue value="${manifest.available+main.class}"/>
+ <isset property="libs.CopyLibs.classpath"/>
+ </and>
+ </condition>
+ <condition property="have.tests">
+ <or>
+ <available file="${test.src.dir}"/>
+ </or>
+ </condition>
+ <condition property="have.sources">
+ <or>
+ <available file="${src.dir}"/>
+ </or>
+ </condition>
+ <condition property="netbeans.home+have.tests">
+ <and>
+ <isset property="netbeans.home"/>
+ <isset property="have.tests"/>
+ </and>
+ </condition>
+ <condition property="no.javadoc.preview">
+ <and>
+ <isset property="javadoc.preview"/>
+ <isfalse value="${javadoc.preview}"/>
+ </and>
+ </condition>
+ <property name="run.jvmargs" value=""/>
+ <property name="javac.compilerargs" value=""/>
+ <property name="work.dir" value="${basedir}"/>
+ <condition property="no.deps">
+ <and>
+ <istrue value="${no.dependencies}"/>
+ </and>
+ </condition>
+ <property name="javac.debug" value="true"/>
+ <property name="javadoc.preview" value="true"/>
+ <property name="application.args" value=""/>
+ <property name="source.encoding" value="${file.encoding}"/>
+ <condition property="javadoc.encoding.used" value="${javadoc.encoding}">
+ <and>
+ <isset property="javadoc.encoding"/>
+ <not>
+ <equals arg1="${javadoc.encoding}" arg2=""/>
+ </not>
+ </and>
+ </condition>
+ <property name="javadoc.encoding.used" value="${source.encoding}"/>
+ <property name="includes" value="**"/>
+ <property name="excludes" value=""/>
+ <property name="do.depend" value="false"/>
+ <condition property="do.depend.true">
+ <istrue value="${do.depend}"/>
+ </condition>
+ <condition else="" property="javac.compilerargs.jaxws" value="-Djava.endorsed.dirs='${jaxws.endorsed.dir}'">
+ <and>
+ <isset property="jaxws.endorsed.dir"/>
+ <available file="nbproject/jaxws-build.xml"/>
+ </and>
+ </condition>
+ </target>
+ <target name="-post-init">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="-pre-init,-init-private,-init-user,-init-project,-do-init" name="-init-check">
+ <fail unless="src.dir">Must set src.dir</fail>
+ <fail unless="test.src.dir">Must set test.src.dir</fail>
+ <fail unless="build.dir">Must set build.dir</fail>
+ <fail unless="dist.dir">Must set dist.dir</fail>
+ <fail unless="build.classes.dir">Must set build.classes.dir</fail>
+ <fail unless="dist.javadoc.dir">Must set dist.javadoc.dir</fail>
+ <fail unless="build.test.classes.dir">Must set build.test.classes.dir</fail>
+ <fail unless="build.test.results.dir">Must set build.test.results.dir</fail>
+ <fail unless="build.classes.excludes">Must set build.classes.excludes</fail>
+ <fail unless="dist.jar">Must set dist.jar</fail>
+ </target>
+ <target name="-init-macrodef-property">
+ <macrodef name="property" uri="http://www.netbeans.org/ns/j2se-project/1">
+ <attribute name="name"/>
+ <attribute name="value"/>
+ <sequential>
+ <property name="@{name}" value="${@{value}}"/>
+ </sequential>
+ </macrodef>
+ </target>
+ <target name="-init-macrodef-javac">
+ <macrodef name="javac" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${src.dir}" name="srcdir"/>
+ <attribute default="${build.classes.dir}" name="destdir"/>
+ <attribute default="${javac.classpath}" name="classpath"/>
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="${javac.debug}" name="debug"/>
+ <attribute default="${empty.dir}" name="sourcepath"/>
+ <attribute default="${empty.dir}" name="gensrcdir"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <property location="${build.dir}/empty" name="empty.dir"/>
+ <mkdir dir="${empty.dir}"/>
+ <javac debug="@{debug}" deprecation="${javac.deprecation}" destdir="@{destdir}" encoding="${source.encoding}" excludes="@{excludes}" includeantruntime="false" includes="@{includes}" source="${javac.source}" sourcepath="@{sourcepath}" srcdir="@{srcdir}" target="${javac.target}">
+ <src>
+ <dirset dir="@{gensrcdir}" erroronmissingdir="false">
+ <include name="*"/>
+ </dirset>
+ </src>
+ <classpath>
+ <path path="@{classpath}"/>
+ </classpath>
+ <compilerarg line="${javac.compilerargs} ${javac.compilerargs.jaxws}"/>
+ <customize/>
+ </javac>
+ </sequential>
+ </macrodef>
+ <macrodef name="depend" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${src.dir}" name="srcdir"/>
+ <attribute default="${build.classes.dir}" name="destdir"/>
+ <attribute default="${javac.classpath}" name="classpath"/>
+ <sequential>
+ <depend cache="${build.dir}/depcache" destdir="@{destdir}" excludes="${excludes}" includes="${includes}" srcdir="@{srcdir}">
+ <classpath>
+ <path path="@{classpath}"/>
+ </classpath>
+ </depend>
+ </sequential>
+ </macrodef>
+ <macrodef name="force-recompile" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${build.classes.dir}" name="destdir"/>
+ <sequential>
+ <fail unless="javac.includes">Must set javac.includes</fail>
+ <pathconvert pathsep="," property="javac.includes.binary">
+ <path>
+ <filelist dir="@{destdir}" files="${javac.includes}"/>
+ </path>
+ <globmapper from="*.java" to="*.class"/>
+ </pathconvert>
+ <delete>
+ <files includes="${javac.includes.binary}"/>
+ </delete>
+ </sequential>
+ </macrodef>
+ </target>
+ <target name="-init-macrodef-junit">
+ <macrodef name="junit" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <sequential>
+ <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" showoutput="true">
+ <batchtest todir="${build.test.results.dir}">
+ <fileset dir="${test.src.dir}" excludes="@{excludes},${excludes}" includes="@{includes}">
+ <filename name="@{testincludes}"/>
+ </fileset>
+ </batchtest>
+ <classpath>
+ <path path="${run.test.classpath}"/>
+ </classpath>
+ <syspropertyset>
+ <propertyref prefix="test-sys-prop."/>
+ <mapper from="test-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ <formatter type="brief" usefile="false"/>
+ <formatter type="xml"/>
+ <jvmarg line="${run.jvmargs}"/>
+ </junit>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-debug-args" name="-init-macrodef-nbjpda">
+ <macrodef name="nbjpdastart" uri="http://www.netbeans.org/ns/j2se-project/1">
+ <attribute default="${main.class}" name="name"/>
+ <attribute default="${debug.classpath}" name="classpath"/>
+ <attribute default="" name="stopclassname"/>
+ <sequential>
+ <nbjpdastart addressproperty="jpda.address" name="@{name}" stopclassname="@{stopclassname}" transport="${debug-transport}">
+ <classpath>
+ <path path="@{classpath}"/>
+ </classpath>
+ </nbjpdastart>
+ </sequential>
+ </macrodef>
+ <macrodef name="nbjpdareload" uri="http://www.netbeans.org/ns/j2se-project/1">
+ <attribute default="${build.classes.dir}" name="dir"/>
+ <sequential>
+ <nbjpdareload>
+ <fileset dir="@{dir}" includes="${fix.classes}">
+ <include name="${fix.includes}*.class"/>
+ </fileset>
+ </nbjpdareload>
+ </sequential>
+ </macrodef>
+ </target>
+ <target name="-init-debug-args">
+ <property name="version-output" value="java version &quot;${ant.java.version}"/>
+ <condition property="have-jdk-older-than-1.4">
+ <or>
+ <contains string="${version-output}" substring="java version &quot;1.0"/>
+ <contains string="${version-output}" substring="java version &quot;1.1"/>
+ <contains string="${version-output}" substring="java version &quot;1.2"/>
+ <contains string="${version-output}" substring="java version &quot;1.3"/>
+ </or>
+ </condition>
+ <condition else="-Xdebug" property="debug-args-line" value="-Xdebug -Xnoagent -Djava.compiler=none">
+ <istrue value="${have-jdk-older-than-1.4}"/>
+ </condition>
+ <condition else="dt_socket" property="debug-transport-by-os" value="dt_shmem">
+ <os family="windows"/>
+ </condition>
+ <condition else="${debug-transport-by-os}" property="debug-transport" value="${debug.transport}">
+ <isset property="debug.transport"/>
+ </condition>
+ </target>
+ <target depends="-init-debug-args" name="-init-macrodef-debug">
+ <macrodef name="debug" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${main.class}" name="classname"/>
+ <attribute default="${debug.classpath}" name="classpath"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <java classname="@{classname}" dir="${work.dir}" fork="true">
+ <jvmarg line="${debug-args-line}"/>
+ <jvmarg value="-Xrunjdwp:transport=${debug-transport},address=${jpda.address}"/>
+ <jvmarg value="-Dfile.encoding=${source.encoding}"/>
+ <redirector errorencoding="${source.encoding}" inputencoding="${source.encoding}" outputencoding="${source.encoding}"/>
+ <jvmarg line="${run.jvmargs}"/>
+ <classpath>
+ <path path="@{classpath}"/>
+ </classpath>
+ <syspropertyset>
+ <propertyref prefix="run-sys-prop."/>
+ <mapper from="run-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ <customize/>
+ </java>
+ </sequential>
+ </macrodef>
+ </target>
+ <target name="-init-macrodef-java">
+ <macrodef name="java" uri="http://www.netbeans.org/ns/j2se-project/1">
+ <attribute default="${main.class}" name="classname"/>
+ <attribute default="${run.classpath}" name="classpath"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <java classname="@{classname}" dir="${work.dir}" fork="true">
+ <jvmarg value="-Dfile.encoding=${source.encoding}"/>
+ <redirector errorencoding="${source.encoding}" inputencoding="${source.encoding}" outputencoding="${source.encoding}"/>
+ <jvmarg line="${run.jvmargs}"/>
+ <classpath>
+ <path path="@{classpath}"/>
+ </classpath>
+ <syspropertyset>
+ <propertyref prefix="run-sys-prop."/>
+ <mapper from="run-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ <customize/>
+ </java>
+ </sequential>
+ </macrodef>
+ </target>
+ <target name="-init-presetdef-jar">
+ <presetdef name="jar" uri="http://www.netbeans.org/ns/j2se-project/1">
+ <jar compress="${jar.compress}" jarfile="${dist.jar}">
+ <j2seproject1:fileset dir="${build.classes.dir}"/>
+ </jar>
+ </presetdef>
+ </target>
+ <target depends="-pre-init,-init-private,-init-user,-init-project,-do-init,-post-init,-init-check,-init-macrodef-property,-init-macrodef-javac,-init-macrodef-junit,-init-macrodef-nbjpda,-init-macrodef-debug,-init-macrodef-java,-init-presetdef-jar" name="init"/>
+ <!--
+ ===================
+ COMPILATION SECTION
+ ===================
+ -->
+ <target depends="init" name="deps-jar" unless="no.deps"/>
+ <target depends="init,-check-automatic-build,-clean-after-automatic-build" name="-verify-automatic-build"/>
+ <target depends="init" name="-check-automatic-build">
+ <available file="${build.classes.dir}/.netbeans_automatic_build" property="netbeans.automatic.build"/>
+ </target>
+ <target depends="init" if="netbeans.automatic.build" name="-clean-after-automatic-build">
+ <antcall target="clean"/>
+ </target>
+ <target depends="init,deps-jar" name="-pre-pre-compile">
+ <mkdir dir="${build.classes.dir}"/>
+ </target>
+ <target name="-pre-compile">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target if="do.depend.true" name="-compile-depend">
+ <pathconvert property="build.generated.subdirs">
+ <dirset dir="${build.generated.sources.dir}" erroronmissingdir="false">
+ <include name="*"/>
+ </dirset>
+ </pathconvert>
+ <j2seproject3:depend srcdir="${src.dir}:${build.generated.subdirs}"/>
+ </target>
+ <target depends="init,deps-jar,-pre-pre-compile,-pre-compile,-compile-depend" if="have.sources" name="-do-compile">
+ <j2seproject3:javac gensrcdir="${build.generated.sources.dir}"/>
+ <copy todir="${build.classes.dir}">
+ <fileset dir="${src.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
+ </copy>
+ </target>
+ <target name="-post-compile">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,deps-jar,-verify-automatic-build,-pre-pre-compile,-pre-compile,-do-compile,-post-compile" description="Compile project." name="compile"/>
+ <target name="-pre-compile-single">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,deps-jar,-pre-pre-compile" name="-do-compile-single">
+ <fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
+ <j2seproject3:force-recompile/>
+ <j2seproject3:javac excludes="" gensrcdir="${build.generated.sources.dir}" includes="${javac.includes}" sourcepath="${src.dir}"/>
+ </target>
+ <target name="-post-compile-single">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,deps-jar,-verify-automatic-build,-pre-pre-compile,-pre-compile-single,-do-compile-single,-post-compile-single" name="compile-single"/>
+ <!--
+ ====================
+ JAR BUILDING SECTION
+ ====================
+ -->
+ <target depends="init" name="-pre-pre-jar">
+ <dirname file="${dist.jar}" property="dist.jar.dir"/>
+ <mkdir dir="${dist.jar.dir}"/>
+ </target>
+ <target name="-pre-jar">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,compile,-pre-pre-jar,-pre-jar" name="-do-jar-without-manifest" unless="manifest.available">
+ <j2seproject1:jar/>
+ </target>
+ <target depends="init,compile,-pre-pre-jar,-pre-jar" if="manifest.available" name="-do-jar-with-manifest" unless="manifest.available+main.class">
+ <j2seproject1:jar manifest="${manifest.file}"/>
+ </target>
+ <target depends="init,compile,-pre-pre-jar,-pre-jar" if="manifest.available+main.class" name="-do-jar-with-mainclass" unless="manifest.available+main.class+mkdist.available">
+ <j2seproject1:jar manifest="${manifest.file}">
+ <j2seproject1:manifest>
+ <j2seproject1:attribute name="Main-Class" value="${main.class}"/>
+ </j2seproject1:manifest>
+ </j2seproject1:jar>
+ <echo>To run this application from the command line without Ant, try:</echo>
+ <property location="${build.classes.dir}" name="build.classes.dir.resolved"/>
+ <property location="${dist.jar}" name="dist.jar.resolved"/>
+ <pathconvert property="run.classpath.with.dist.jar">
+ <path path="${run.classpath}"/>
+ <map from="${build.classes.dir.resolved}" to="${dist.jar.resolved}"/>
+ </pathconvert>
+ <echo>java -cp "${run.classpath.with.dist.jar}" ${main.class}</echo>
+ </target>
+ <target depends="init,compile,-pre-pre-jar,-pre-jar" if="manifest.available+main.class+mkdist.available" name="-do-jar-with-libraries">
+ <property location="${build.classes.dir}" name="build.classes.dir.resolved"/>
+ <pathconvert property="run.classpath.without.build.classes.dir">
+ <path path="${run.classpath}"/>
+ <map from="${build.classes.dir.resolved}" to=""/>
+ </pathconvert>
+ <pathconvert pathsep=" " property="jar.classpath">
+ <path path="${run.classpath.without.build.classes.dir}"/>
+ <chainedmapper>
+ <flattenmapper/>
+ <globmapper from="*" to="lib/*"/>
+ </chainedmapper>
+ </pathconvert>
+ <taskdef classname="org.netbeans.modules.java.j2seproject.copylibstask.CopyLibs" classpath="${libs.CopyLibs.classpath}" name="copylibs"/>
+ <copylibs compress="${jar.compress}" jarfile="${dist.jar}" manifest="${manifest.file}" runtimeclasspath="${run.classpath.without.build.classes.dir}">
+ <fileset dir="${build.classes.dir}"/>
+ <manifest>
+ <attribute name="Main-Class" value="${main.class}"/>
+ <attribute name="Class-Path" value="${jar.classpath}"/>
+ </manifest>
+ </copylibs>
+ <echo>To run this application from the command line without Ant, try:</echo>
+ <property location="${dist.jar}" name="dist.jar.resolved"/>
+ <echo>java -jar "${dist.jar.resolved}"</echo>
+ </target>
+ <target depends="init,compile,-pre-pre-jar,-pre-jar" if="libs.CopyLibs.classpath" name="-do-jar-with-libraries-without-manifest" unless="manifest.available+main.class">
+ <property location="${build.classes.dir}" name="build.classes.dir.resolved"/>
+ <pathconvert property="run.classpath.without.build.classes.dir">
+ <path path="${run.classpath}"/>
+ <map from="${build.classes.dir.resolved}" to=""/>
+ </pathconvert>
+ <pathconvert pathsep=" " property="jar.classpath">
+ <path path="${run.classpath.without.build.classes.dir}"/>
+ <chainedmapper>
+ <flattenmapper/>
+ <globmapper from="*" to="lib/*"/>
+ </chainedmapper>
+ </pathconvert>
+ <taskdef classname="org.netbeans.modules.java.j2seproject.copylibstask.CopyLibs" classpath="${libs.CopyLibs.classpath}" name="copylibs"/>
+ <copylibs compress="${jar.compress}" jarfile="${dist.jar}" runtimeclasspath="${run.classpath.without.build.classes.dir}">
+ <fileset dir="${build.classes.dir}"/>
+ </copylibs>
+ </target>
+ <target name="-post-jar">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,compile,-pre-jar,-do-jar-with-manifest,-do-jar-without-manifest,-do-jar-with-mainclass,-do-jar-with-libraries,-do-jar-with-libraries-without-manifest,-post-jar" description="Build JAR." name="jar"/>
+ <!--
+ =================
+ EXECUTION SECTION
+ =================
+ -->
+ <target depends="init,compile" description="Run a main class." name="run">
+ <j2seproject1:java>
+ <customize>
+ <arg line="${application.args}"/>
+ </customize>
+ </j2seproject1:java>
+ </target>
+ <target name="-do-not-recompile">
+ <property name="javac.includes.binary" value=""/>
+ </target>
+ <target depends="init,-do-not-recompile,compile-single" name="run-single">
+ <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
+ <j2seproject1:java classname="${run.class}"/>
+ </target>
+ <target depends="init,-do-not-recompile,compile-test-single" name="run-test-with-main">
+ <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
+ <j2seproject1:java classname="${run.class}" classpath="${run.test.classpath}"/>
+ </target>
+ <!--
+ =================
+ DEBUGGING SECTION
+ =================
+ -->
+ <target depends="init" if="netbeans.home" name="-debug-start-debugger">
+ <j2seproject1:nbjpdastart name="${debug.class}"/>
+ </target>
+ <target depends="init" if="netbeans.home" name="-debug-start-debugger-main-test">
+ <j2seproject1:nbjpdastart classpath="${debug.test.classpath}" name="${debug.class}"/>
+ </target>
+ <target depends="init,compile" name="-debug-start-debuggee">
+ <j2seproject3:debug>
+ <customize>
+ <arg line="${application.args}"/>
+ </customize>
+ </j2seproject3:debug>
+ </target>
+ <target depends="init,compile,-debug-start-debugger,-debug-start-debuggee" description="Debug project in IDE." if="netbeans.home" name="debug"/>
+ <target depends="init" if="netbeans.home" name="-debug-start-debugger-stepinto">
+ <j2seproject1:nbjpdastart stopclassname="${main.class}"/>
+ </target>
+ <target depends="init,compile,-debug-start-debugger-stepinto,-debug-start-debuggee" if="netbeans.home" name="debug-stepinto"/>
+ <target depends="init,compile-single" if="netbeans.home" name="-debug-start-debuggee-single">
+ <fail unless="debug.class">Must select one file in the IDE or set debug.class</fail>
+ <j2seproject3:debug classname="${debug.class}"/>
+ </target>
+ <target depends="init,-do-not-recompile,compile-single,-debug-start-debugger,-debug-start-debuggee-single" if="netbeans.home" name="debug-single"/>
+ <target depends="init,compile-test-single" if="netbeans.home" name="-debug-start-debuggee-main-test">
+ <fail unless="debug.class">Must select one file in the IDE or set debug.class</fail>
+ <j2seproject3:debug classname="${debug.class}" classpath="${debug.test.classpath}"/>
+ </target>
+ <target depends="init,-do-not-recompile,compile-test-single,-debug-start-debugger-main-test,-debug-start-debuggee-main-test" if="netbeans.home" name="debug-test-with-main"/>
+ <target depends="init" name="-pre-debug-fix">
+ <fail unless="fix.includes">Must set fix.includes</fail>
+ <property name="javac.includes" value="${fix.includes}.java"/>
+ </target>
+ <target depends="init,-pre-debug-fix,compile-single" if="netbeans.home" name="-do-debug-fix">
+ <j2seproject1:nbjpdareload/>
+ </target>
+ <target depends="init,-pre-debug-fix,-do-debug-fix" if="netbeans.home" name="debug-fix"/>
+ <!--
+ ===============
+ JAVADOC SECTION
+ ===============
+ -->
+ <target depends="init" name="-javadoc-build">
+ <mkdir dir="${dist.javadoc.dir}"/>
+ <javadoc additionalparam="${javadoc.additionalparam}" author="${javadoc.author}" charset="UTF-8" destdir="${dist.javadoc.dir}" docencoding="UTF-8" encoding="${javadoc.encoding.used}" failonerror="true" noindex="${javadoc.noindex}" nonavbar="${javadoc.nonavbar}" notree="${javadoc.notree}" private="${javadoc.private}" source="${javac.source}" splitindex="${javadoc.splitindex}" use="${javadoc.use}" useexternalfile="true" version="${javadoc.version}" windowtitle="${javadoc.windowtitle}">
+ <classpath>
+ <path path="${javac.classpath}"/>
+ </classpath>
+ <fileset dir="${src.dir}" excludes="${excludes}" includes="${includes}">
+ <filename name="**/*.java"/>
+ </fileset>
+ <fileset dir="${build.generated.sources.dir}" erroronmissingdir="false">
+ <include name="**/*.java"/>
+ </fileset>
+ </javadoc>
+ </target>
+ <target depends="init,-javadoc-build" if="netbeans.home" name="-javadoc-browse" unless="no.javadoc.preview">
+ <nbbrowse file="${dist.javadoc.dir}/index.html"/>
+ </target>
+ <target depends="init,-javadoc-build,-javadoc-browse" description="Build Javadoc." name="javadoc"/>
+ <!--
+ =========================
+ JUNIT COMPILATION SECTION
+ =========================
+ -->
+ <target depends="init,compile" if="have.tests" name="-pre-pre-compile-test">
+ <mkdir dir="${build.test.classes.dir}"/>
+ </target>
+ <target name="-pre-compile-test">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target if="do.depend.true" name="-compile-test-depend">
+ <j2seproject3:depend classpath="${javac.test.classpath}" destdir="${build.test.classes.dir}" srcdir="${test.src.dir}"/>
+ </target>
+ <target depends="init,compile,-pre-pre-compile-test,-pre-compile-test,-compile-test-depend" if="have.tests" name="-do-compile-test">
+ <j2seproject3:javac classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" srcdir="${test.src.dir}"/>
+ <copy todir="${build.test.classes.dir}">
+ <fileset dir="${test.src.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
+ </copy>
+ </target>
+ <target name="-post-compile-test">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,compile,-pre-pre-compile-test,-pre-compile-test,-do-compile-test,-post-compile-test" name="compile-test"/>
+ <target name="-pre-compile-test-single">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,compile,-pre-pre-compile-test,-pre-compile-test-single" if="have.tests" name="-do-compile-test-single">
+ <fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
+ <j2seproject3:force-recompile destdir="${build.test.classes.dir}"/>
+ <j2seproject3:javac classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" excludes="" includes="${javac.includes}" sourcepath="${test.src.dir}" srcdir="${test.src.dir}"/>
+ <copy todir="${build.test.classes.dir}">
+ <fileset dir="${test.src.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
+ </copy>
+ </target>
+ <target name="-post-compile-test-single">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,compile,-pre-pre-compile-test,-pre-compile-test-single,-do-compile-test-single,-post-compile-test-single" name="compile-test-single"/>
+ <!--
+ =======================
+ JUNIT EXECUTION SECTION
+ =======================
+ -->
+ <target depends="init" if="have.tests" name="-pre-test-run">
+ <mkdir dir="${build.test.results.dir}"/>
+ </target>
+ <target depends="init,compile-test,-pre-test-run" if="have.tests" name="-do-test-run">
+ <j2seproject3:junit testincludes="**/*Test.java"/>
+ </target>
+ <target depends="init,compile-test,-pre-test-run,-do-test-run" if="have.tests" name="-post-test-run">
+ <fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
+ </target>
+ <target depends="init" if="have.tests" name="test-report"/>
+ <target depends="init" if="netbeans.home+have.tests" name="-test-browse"/>
+ <target depends="init,compile-test,-pre-test-run,-do-test-run,test-report,-post-test-run,-test-browse" description="Run unit tests." name="test"/>
+ <target depends="init" if="have.tests" name="-pre-test-run-single">
+ <mkdir dir="${build.test.results.dir}"/>
+ </target>
+ <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-do-test-run-single">
+ <fail unless="test.includes">Must select some files in the IDE or set test.includes</fail>
+ <j2seproject3:junit excludes="" includes="${test.includes}"/>
+ </target>
+ <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single" if="have.tests" name="-post-test-run-single">
+ <fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
+ </target>
+ <target depends="init,-do-not-recompile,compile-test-single,-pre-test-run-single,-do-test-run-single,-post-test-run-single" description="Run single unit test." name="test-single"/>
+ <!--
+ =======================
+ JUNIT DEBUGGING SECTION
+ =======================
+ -->
+ <target depends="init,compile-test" if="have.tests" name="-debug-start-debuggee-test">
+ <fail unless="test.class">Must select one file in the IDE or set test.class</fail>
+ <property location="${build.test.results.dir}/TEST-${test.class}.xml" name="test.report.file"/>
+ <delete file="${test.report.file}"/>
+ <mkdir dir="${build.test.results.dir}"/>
+ <j2seproject3:debug classname="org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner" classpath="${ant.home}/lib/ant.jar:${ant.home}/lib/ant-junit.jar:${debug.test.classpath}">
+ <customize>
+ <syspropertyset>
+ <propertyref prefix="test-sys-prop."/>
+ <mapper from="test-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ <arg value="${test.class}"/>
+ <arg value="showoutput=true"/>
+ <arg value="formatter=org.apache.tools.ant.taskdefs.optional.junit.BriefJUnitResultFormatter"/>
+ <arg value="formatter=org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter,${test.report.file}"/>
+ </customize>
+ </j2seproject3:debug>
+ </target>
+ <target depends="init,compile-test" if="netbeans.home+have.tests" name="-debug-start-debugger-test">
+ <j2seproject1:nbjpdastart classpath="${debug.test.classpath}" name="${test.class}"/>
+ </target>
+ <target depends="init,-do-not-recompile,compile-test-single,-debug-start-debugger-test,-debug-start-debuggee-test" name="debug-test"/>
+ <target depends="init,-pre-debug-fix,compile-test-single" if="netbeans.home" name="-do-debug-fix-test">
+ <j2seproject1:nbjpdareload dir="${build.test.classes.dir}"/>
+ </target>
+ <target depends="init,-pre-debug-fix,-do-debug-fix-test" if="netbeans.home" name="debug-fix-test"/>
+ <!--
+ =========================
+ APPLET EXECUTION SECTION
+ =========================
+ -->
+ <target depends="init,compile-single" name="run-applet">
+ <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
+ <j2seproject1:java classname="sun.applet.AppletViewer">
+ <customize>
+ <arg value="${applet.url}"/>
+ </customize>
+ </j2seproject1:java>
+ </target>
+ <!--
+ =========================
+ APPLET DEBUGGING SECTION
+ =========================
+ -->
+ <target depends="init,compile-single" if="netbeans.home" name="-debug-start-debuggee-applet">
+ <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
+ <j2seproject3:debug classname="sun.applet.AppletViewer">
+ <customize>
+ <arg value="${applet.url}"/>
+ </customize>
+ </j2seproject3:debug>
+ </target>
+ <target depends="init,compile-single,-debug-start-debugger,-debug-start-debuggee-applet" if="netbeans.home" name="debug-applet"/>
+ <!--
+ ===============
+ CLEANUP SECTION
+ ===============
+ -->
+ <target depends="init" name="deps-clean" unless="no.deps"/>
+ <target depends="init" name="-do-clean">
+ <delete dir="${build.dir}"/>
+ <delete dir="${dist.dir}"/>
+ </target>
+ <target name="-post-clean">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,deps-clean,-do-clean,-post-clean" description="Clean build products." name="clean"/>
+</project>
View
8 nbproject/genfiles.properties
@@ -0,0 +1,8 @@
+build.xml.data.CRC32=82862954
+build.xml.script.CRC32=4ee68ef3
+build.xml.stylesheet.CRC32=be360661
+# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
+# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
+nbproject/build-impl.xml.data.CRC32=82862954
+nbproject/build-impl.xml.script.CRC32=303f4a0e
+nbproject/build-impl.xml.stylesheet.CRC32=5c621a33@1.26.2.45
View
67 nbproject/project.properties
@@ -0,0 +1,67 @@
+application.homepage=http://json-jruby.rubyforge.org/
+application.title=JSON-JRuby
+application.vendor=mernen
+build.classes.dir=${build.dir}/classes
+build.classes.excludes=**/*.java,**/*.form
+# This directory is removed when the project is cleaned:
+build.dir=build
+build.generated.sources.dir=${build.dir}/generated-sources
+lib.dir=lib
+jruby.dir=../jruby
+build.generated.dir=${build.dir}/generated
+generator.jar=${lib.dir}/json/ext/generator.jar
+parser.jar=${lib.dir}/json/ext/parser.jar
+# Only compile against the classpath explicitly listed here:
+build.sysclasspath=ignore
+build.test.classes.dir=${build.dir}/test/classes
+build.test.results.dir=${build.dir}/test/results
+debug.classpath=\
+ ${run.classpath}
+debug.test.classpath=\
+ ${run.test.classpath}
+# This directory is removed when the project is cleaned:
+dist.dir=dist
+dist.jar=${dist.dir}/ext.jar
+dist.javadoc.dir=${dist.dir}/javadoc
+excludes=
+file.reference.jruby.jar=${jruby.dir}/lib/jruby.jar
+file.reference.bytelist.jar=${jruby.dir}/build_lib/bytelist.jar
+includes=**
+jar.compress=false
+javac.classpath=\
+ ${file.reference.jruby.jar}:\
+ ${file.reference.bytelist.jar}
+# Space-separated list of extra javac options
+javac.compilerargs=-Xlint -Xlint:-serial
+javac.deprecation=true
+javac.source=1.5
+javac.target=1.5
+javac.test.classpath=\
+ ${javac.classpath}:\
+ ${build.classes.dir}
+javadoc.additionalparam=
+javadoc.author=false
+javadoc.encoding=${source.encoding}
+javadoc.noindex=false
+javadoc.nonavbar=false
+javadoc.notree=false
+javadoc.private=false
+javadoc.splitindex=true
+javadoc.use=true
+javadoc.version=false
+javadoc.windowtitle=
+meta.inf.dir=${src.dir}/META-INF
+platform.active=default_platform
+run.classpath=\
+ ${javac.classpath}:\
+ ${build.classes.dir}
+# Space-separated list of JVM arguments used when running the project
+# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value
+# or test-sys-prop.name=value to set system properties for unit tests):
+run.jvmargs=
+run.test.classpath=\
+ ${javac.test.classpath}:\
+ ${build.test.classes.dir}
+source.encoding=UTF-8
+src.dir=src
+test.src.dir=test
View
16 nbproject/project.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://www.netbeans.org/ns/project/1">
+ <type>org.netbeans.modules.java.j2seproject</type>
+ <configuration>
+ <data xmlns="http://www.netbeans.org/ns/j2se-project/3">
+ <name>JSON-JRuby</name>
+ <minimum-ant-version>1.6.5</minimum-ant-version>
+ <source-roots>
+ <root id="src.dir"/>
+ </source-roots>
+ <test-roots>
+ <root id="test.src.dir"/>
+ </test-roots>
+ </data>
+ </configuration>
+</project>
View
167 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();
+ }
+}
View
452 src/json/ext/Generator.java
@@ -0,0 +1,452 @@
+/*
+ * 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);
+ int depth = args.length > 1 ? RubyNumeric.fix2int(args[1]) : 0;
+ return session.infect(handler.generateNew(session, object, depth));
+ }
+
+ /**
+ * 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, int depth) {
+ Session session = new Session(context, config);
+ Handler<? super T> handler = getHandlerFor(context.getRuntime(), object);
+ return handler.generateNew(session, object, depth);
+ }
+
+ /**
+ * 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, int depth) {
+ return 4;
+ }
+
+ RubyString generateNew(Session session, T object, int depth) {
+ ByteList buffer = new ByteList(guessSize(session, object, depth));
+ generate(session, object, buffer, depth);
+ return RubyString.newString(session.getRuntime(), buffer);
+ }
+
+ abstract void generate(Session session, T object, ByteList buffer,
+ int depth);
+ }
+
+ /**
+ * 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, int depth) {
+ return keyword.length();
+ }
+
+ @Override
+ RubyString generateNew(Session session, T object, int depth) {
+ return RubyString.newStringShared(session.getRuntime(), keyword);
+ }
+
+ @Override
+ void generate(Session session, T object, ByteList buffer, int depth) {
+ buffer.append(keyword);
+ }
+ }
+
+
+ /* Handlers */
+
+ static final Handler<RubyBignum> BIGNUM_HANDLER =
+ new Handler<RubyBignum>() {
+ @Override
+ void generate(Session session, RubyBignum object, ByteList buffer,
+ int depth) {
+ // 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,
+ int depth) {
+ buffer.append(object.to_s().getByteList());
+ }
+ };
+
+ static final Handler<RubyFloat> FLOAT_HANDLER =
+ new Handler<RubyFloat>() {
+ @Override
+ void generate(Session session, RubyFloat object, ByteList buffer,
+ int depth) {
+ 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, int depth) {
+ GeneratorState state = session.getState();
+ 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,
+ int depth) {
+ ThreadContext context = session.getContext();
+ Ruby runtime = context.getRuntime();
+ GeneratorState state = session.getState();
+ state.checkMaxNesting(context, depth + 1);
+
+ ByteList indentUnit = state.getIndent();
+ byte[] shift = Utils.repeat(indentUnit, depth + 1);
+
+ 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, depth + 1);
+ }
+
+ if (arrayNl.length() != 0) {
+ buffer.append(arrayNl);
+ buffer.append(shift, 0, depth * indentUnit.length());
+ }
+
+ buffer.append((byte)']');
+ }
+ };
+
+ static final Handler<RubyHash> HASH_HANDLER =
+ new Handler<RubyHash>() {
+ @Override
+ int guessSize(Session session, RubyHash object, int depth) {
+ GeneratorState state = session.getState();
+ int perItem =
+ 12 // key, colon, comma
+ + (depth + 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, final int depth) {
+ ThreadContext context = session.getContext();
+ final Ruby runtime = context.getRuntime();
+ final GeneratorState state = session.getState();
+ state.checkMaxNesting(context, depth + 1);
+
+ final ByteList objectNl = state.getObjectNl();
+ final byte[] indent = Utils.repeat(state.getIndent(), depth + 1);
+ 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, depth + 1);
+ session.infectBy(key);
+
+ buffer.append(spaceBefore);
+ buffer.append((byte)':');
+ buffer.append(space);
+
+ Handler<IRubyObject> valueHandler = getHandlerFor(runtime, value);
+ valueHandler.generate(session, value, buffer, depth + 1);
+ session.infectBy(value);
+ }
+ });
+ if (objectNl.length() != 0) {
+ buffer.append(objectNl);
+ if (indent.length != 0) {
+ for (int i = 0; i < depth; i++) {
+ buffer.append(indent);
+ }
+ }
+ }
+ buffer.append((byte)'}');
+ }
+ };
+
+ static final Handler<RubyString> STRING_HANDLER =
+ new Handler<RubyString>() {
+ @Override
+ int guessSize(Session session, RubyString object, int depth) {
+ // 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,
+ int depth) {
+ 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,
+ int depth) {
+ RubyString str = object.asString();
+ return STRING_HANDLER.generateNew(session, str, depth);
+ }
+
+ @Override
+ void generate(Session session, IRubyObject object, ByteList buffer,
+ int depth) {
+ RubyString str = object.asString();
+ STRING_HANDLER.generate(session, str, buffer, depth);
+ }
+ };
+
+ /**
+ * A handler that simply calls <code>#to_json(state, depth)</code> on the
+ * given object.
+ */
+ static final Handler<IRubyObject> GENERIC_HANDLER =
+ new Handler<IRubyObject>() {
+ @Override
+ RubyString generateNew(Session session, IRubyObject object,
+ int depth) {
+ RubyNumeric vDepth =
+ RubyNumeric.int2fix(session.getRuntime(), depth);
+ IRubyObject result =
+ object.callMethod(session.getContext(), "to_json",
+ new IRubyObject[] {session.getState(), vDepth});
+ 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,
+ int depth) {
+ RubyString result = generateNew(session, object, depth);
+ buffer.append(result.getByteList());
+ }
+ };
+}
View
231 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);
+ }
+ };
+}
View
42 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;
+ }
+}
View
443 src/json/ext/GeneratorState.java
@@ -0,0 +1,443 @@
+/*
+ * 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;
+
+ 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 info.getSafeStatePrototype(context);
+ }
+
+ /**
+ * <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;
+ return this;
+ }
+
+ /**
+ * XXX
+ */
+ @JRubyMethod
+ public IRubyObject generate(ThreadContext context, IRubyObject obj) {
+ RubyString result = Generator.generateJson(context, obj, this, 0);
+ 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;
+ if (b == brace) return true;
+ return false;
+ }
+ 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.</