From 0e046d1b858e9804c5ba579c2c8b9182e96696c9 Mon Sep 17 00:00:00 2001 From: fangyidong Date: Fri, 23 Jan 2009 06:28:59 +0000 Subject: [PATCH] 1.1 candidate --- AUTHORS.txt | 3 +- ChangeLog.txt | 9 + README.txt | 155 +----- VERSION.txt | 2 +- build.xml | 27 +- doc/json.lex | 31 +- lib/json_simple-1.1.jar | Bin 0 -> 16082 bytes src/org/json/simple/ItemList.java | 147 +++++ src/org/json/simple/JSONArray.java | 90 +++- src/org/json/simple/JSONAware.java | 12 + src/org/json/simple/JSONObject.java | 189 +++---- src/org/json/simple/JSONStreamAware.java | 15 + src/org/json/simple/JSONValue.java | 248 ++++++++- .../json/simple/parser/ContainerFactory.java | 23 + .../json/simple/parser/ContentHandler.java | 99 ++++ src/org/json/simple/parser/JSONParser.java | 393 ++++++++++++-- .../json/simple/parser/ParseException.java | 90 ++++ src/org/json/simple/parser/Yylex.java | 503 ++++++++---------- src/org/json/simple/parser/Yytoken.java | 29 +- test/org/json/simple/Test.java | 311 ++++++++++- test/org/json/simple/parser/YylexTest.java | 32 +- 21 files changed, 1803 insertions(+), 605 deletions(-) create mode 100644 lib/json_simple-1.1.jar create mode 100644 src/org/json/simple/ItemList.java create mode 100644 src/org/json/simple/JSONAware.java create mode 100644 src/org/json/simple/JSONStreamAware.java create mode 100644 src/org/json/simple/parser/ContainerFactory.java create mode 100644 src/org/json/simple/parser/ContentHandler.java create mode 100644 src/org/json/simple/parser/ParseException.java diff --git a/AUTHORS.txt b/AUTHORS.txt index e896fc1..9fda0a2 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -1 +1,2 @@ -Fang Yidong \ No newline at end of file +Yidong Fang +Chris Nokleberg \ No newline at end of file diff --git a/ChangeLog.txt b/ChangeLog.txt index fa4dbb2..e6eb6ab 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,5 +1,14 @@ ChangeLog +Version 1.1 (2009/01/23) +* Supports stoppable SAX-like content handler for streaming of JSON text +* Added JSONStreamAware to support streaming JSON text +* Added ContainerFactory to support creating arbitrary Map and List as JSON object and JSON array container during decoding +* Supports any Map and List as JSON object and JSON array container during encoding +* Added interface JSONAware +* Added ParseException to get detail error report while parsing +* Added escaping for Unicode characters that cause problems for browser JS eval + Version 1.02 (2009/01/10) * Updated json.lex to improve performance of the lexer * Removed Rope.java and related junit test diff --git a/README.txt b/README.txt index ec4d4ca..e31e82c 100644 --- a/README.txt +++ b/README.txt @@ -1,153 +1,2 @@ -Simple Java toolkit for JSON (JSON.simple) -========================================== - -http://www.JSON.org/java/json_simple.zip - -1.Why the Simple Java toolkit (also named as JSON.simple) for JSON? - - When I use JSON as the data exchange format between the AJAX client and JSP - for the first time, what worry me mostly is how to encode Java strings and - numbers correctly in the server side so the AJAX client will receive a well - formed JSON data. When I looked into the 'JSON in Java' directory in JSON - website,I found that wrappers to JSONObject and JSONArray can be simpler, - due to the simplicity of JSON itself. So I wrote the JSON.simple package. - -2.Is it simple,really? - - I think so. Take an example: - - import org.json.simple.JSONObject; - - JSONObject obj=new JSONObject(); - obj.put("name","foo"); - obj.put("num",new Integer(100)); - obj.put("balance",new Double(1000.21)); - obj.put("is_vip",new Boolean(true)); - obj.put("nickname",null); - System.out.print(obj); - - Result: - {"nickname":null,"num":100,"balance":1000.21,"is_vip":true,"name":"foo"} - - The JSONObject.toString() will escape controls and specials correctly. - -3.How to use JSON.simple in JSP? - - Take an example in JSP: - - <%@page contentType="text/html; charset=UTF-8"%> - <%@page import="org.json.simple.JSONObject"%> - <% - JSONObject obj=new JSONObject(); - obj.put("name","foo"); - obj.put("num",new Integer(100)); - obj.put("balance",new Double(1000.21)); - obj.put("is_vip",new Boolean(true)); - obj.put("nickname",null); - out.print(obj); - out.flush(); - %> - - So the AJAX client will get the responseText. - -4.Some details about JSONObject? - - JSONObject inherits java.util.HashMap,so it don't have to worry about the - mapping things between keys and values. Feel free to use the Map methods - like get(), put(), and remove() and others. JSONObject.toString() will - combine key value pairs to get the JSON data string. Values will be escaped - into JSON quote string format if it's an instance of java.lang.String. Other - type of instance like java.lang.Number,java.lang.Boolean,null,JSONObject and - JSONArray will NOT escape, just take their java.lang.String.valueOf() result. - null value will be the JSON 'null' in the result. - - It's still correct if you put an instance of JSONObject or JSONArray into an - instance of JSONObject or JSONArray. Take the example about: - - JSONObject obj2=new JSONObject(); - obj2.put("phone","123456"); - obj2.put("zip","7890"); - obj.put("contact",obj2); - System.out.print(obj); - - Result: - {"nickname":null,"num":100,"contact":{"phone":"123456","zip":"7890"},"balance":1000.21,"is_vip":true,"name":"foo"} - - The method JSONObject.escape() is used to escape Java string into JSON quote - string. Controls and specials will be escaped correctly into \b,\f,\r,\n,\t, - \",\\,\/,\uhhhh. - -5.Some detail about JSONArray? - - org.json.simple.JSONArray inherits java.util.ArrayList. Feel free to use the - List methods like get(),add(),remove(),iterator() and so on. The rules of - JSONArray.toString() is similar to JSONObject.toString(). Here's the example: - - import org.json.simple.JSONArray; - - JSONArray array=new JSONArray(); - array.add("hello"); - array.add(new Integer(123)); - array.add(new Boolean(false)); - array.add(null); - array.add(new Double(123.45)); - array.add(obj2);//see above - System.out.print(array); - - Result: - ["hello",123,false,null,123.45,{"phone":"123456","zip":"7890"}] - -6.What is JSONValue for? - - org.json.simple.JSONValue is use to parse JSON data into Java Object. - In JSON, the topmost entity is JSON value, not the JSON object. But - it's not necessary to wrap JSON string,boolean,number and null again, - for the Java has already had the according classes: java.lang.String, - java.lang.Boolean,java.lang.Number and null. The mapping is: - - JSON Java - ------------------------------------------------ - string <=> java.lang.String - number <=> java.lang.Number - true|false <=> java.lang.Boolean - null <=> null - array <=> org.json.simple.JSONArray - object <=> org.json.simple.JSONObject - ------------------------------------------------ - - JSONValue has only one kind of method, JSONValue.parse(), which receives - a java.io.Reader or java.lang.String. Return type of JSONValue.parse() - is according to the mapping above. If the input is incorrect in syntax or - there's exceptions during the parsing, I choose to return null, ignoring - the exception: I have no idea if it's a serious implementaion, but I think - it's convenient to the user. - - Here's the example: - - String s="[0,{\"1\":{\"2\":{\"3\":{\"4\":[5,{\"6\":7}]}}}}]"; - Object obj=JSONValue.parse(s); - JSONArray array=(JSONArray)obj; - System.out.println(array.get(1)); - JSONObject obj2=(JSONObject)array.get(1); - System.out.println(obj2.get("1")); - - Result: - {"1":{"2":{"3":{"4":[5,{"6":7}]}}}} - {"2":{"3":{"4":[5,{"6":7}]}}} - -7.About the author. - - I'm a Java EE developer on Linux. - I'm working on web systems and information retrieval systems. - I also develop 3D games and Flash games. - - You can contact me through: - Fang Yidong - Fang Yidong - -8.Project home. - - http://code.google.com/p/json-simple/ - - -http://www.JSON.org/java/json_simple.zip \ No newline at end of file +Please visit: +http://code.google.com/p/json-simple/ \ No newline at end of file diff --git a/VERSION.txt b/VERSION.txt index e6d5cb8..b123147 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.0.2 \ No newline at end of file +1.1 \ No newline at end of file diff --git a/build.xml b/build.xml index 49a0a1e..a14dcb0 100644 --- a/build.xml +++ b/build.xml @@ -1,5 +1,5 @@ - + @@ -14,7 +14,6 @@ - @@ -24,30 +23,6 @@ target="1.2" source="1.2"/> - - - - - - - - - - - - - - - - - - - - \" { yybegin(YYINITIAL);return new Yytoken(Yytoken.TYPE_VALUE,sb.toString());} + \" { yybegin(YYINITIAL);return new Yytoken(Yytoken.TYPE_VALUE, sb.toString());} {UNESCAPED_CH}+ { sb.append(yytext());} \\\" {sb.append('"');} \\\\ {sb.append('\\');} @@ -28,15 +36,21 @@ UNESCAPED_CH = [^\"\\] \\n {sb.append('\n');} \\r {sb.append('\r');} \\t {sb.append('\t');} - \\u{HEX_D}{HEX_D}{HEX_D}{HEX_D} { int ch=Integer.parseInt(yytext().substring(2),16); - sb.append((char)ch); + \\u{HEX_D}{HEX_D}{HEX_D}{HEX_D} { try{ + int ch=Integer.parseInt(yytext().substring(2),16); + sb.append((char)ch); + } + catch(Exception e){ + throw new ParseException(yychar, ParseException.ERROR_UNEXPECTED_EXCEPTION, e); + } } + \\ {sb.append('\\');} \" { sb.delete(0, sb.length());yybegin(STRING_BEGIN);} - {INT} { Long val=Long.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE,val);} - {DOUBLE} { Double val=Double.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE,val);} - "true"|"false" { Boolean val=Boolean.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE,val);} - "null" { return new Yytoken(Yytoken.TYPE_VALUE,null);} + {INT} { Long val=Long.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE, val);} + {DOUBLE} { Double val=Double.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE, val);} + "true"|"false" { Boolean val=Boolean.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE, val);} + "null" { return new Yytoken(Yytoken.TYPE_VALUE, null);} "{" { return new Yytoken(Yytoken.TYPE_LEFT_BRACE,null);} "}" { return new Yytoken(Yytoken.TYPE_RIGHT_BRACE,null);} "[" { return new Yytoken(Yytoken.TYPE_LEFT_SQUARE,null);} @@ -44,3 +58,4 @@ UNESCAPED_CH = [^\"\\] "," { return new Yytoken(Yytoken.TYPE_COMMA,null);} ":" { return new Yytoken(Yytoken.TYPE_COLON,null);} {WS}+ {} + {FALLBACK_CH} { throw new ParseException(yychar, ParseException.ERROR_UNEXPECTED_CHAR, new Character(yycharat(0)));} \ No newline at end of file diff --git a/lib/json_simple-1.1.jar b/lib/json_simple-1.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..840ead29fa05387c23d39ae5418cb669029b3f0b GIT binary patch literal 16082 zcmb7r1yo$g)-}N)Sn%NP?(XjH?(RW?LvVL@cM{y)-5nYW?hXNd^3BYfnY@{|)?dBu z?%RFO*;Thz)hRniP7(wZ8t7*lvS;P^znk9y>g`)vSc#unTt zlz~Fu5szJmyEdhVDIiHIC^}_O_#VPu!#Kr-hodn0NnRm)Fvf*lhEaw|DmaFra9zdT zFu66kHUa%nnEx&_=v!fI9ZdgI-2ayN`BpzAEF5iZ{)ZUCU&S2Ft?jIg|050ge^sSr zBO4|Bmfyx3BhEiai#r)xOPM=5(HL6kJ33}4Zzv+EBEQ&t8xm0a;46$^CR`C{WoA*9 zttX$|VUV+EP@w0Ht_Kx1LNZ1ZdE)pA^)!1RspB#E5mqqe;NF09=74wFJNG($EloJq zxSyQP)aC49oPDEduO+GD<@v+~$ab3_+LUh1FGU4r69fgfFk3H`mq#MzA$g3{dT_fi zR91Pka59jcCF^U7Yz|t(;xl`PXP1fZm^F*;w{5sIVHVvIwXHpdz1Fg=BjiW=vG15H z8{Zxx-`y!8E0sg`u72a&J2u(m$2UbwH?Zj{Q6AbuGKneJEd}S!2bvDD3Wo zfjuI#SnSLP4;wSKvS@_Y>_NqXCDz(*uy>AU4&QW#RptM}iD7A;tatp>C^KW`Bx74u zBrL9R`qR>~i8+0PIY%%xI^{&`&6J9q@R zBJyGKD#@%_$f0cW$BWB0bu(E{xnR;$x100r|_F;_j_E|3=Md+u1cop&xWg0dSw7dDjy~NW1 zxQGPm*nY;6&dKfLcrvzpvL+A&Gaua}T}|MJr{NGMVmuB9=y?b)*qet9uemToCeTw% zYtLbZFlPPr1i^Ll0c=R@1j!u{R*J4Ffl+w;P(C??-Oqzv7#fE;gx3>fklg&THjG`- zq|+HaoYUNRYuLx@!eyQB^Y^PF^ZhAfSVQ<@GvoSkgo#9T4kB#8Qe2e3Yr|aiizeKg38;!7iKwVru+f+9 zOB|ySm9eoJ>_h1s!OR><)CX3&zcYEY1)rV7ZaxB|4e&p{E_l~;^6Aw$T;kO{6?vj` zg|z2sI_qZUz#c1`5uaml-gTr?juxOavuyS}1vEetOhSsn+`e^}MZAd3m39YDjDT-*RtWLv_oH7Lir{Uh@ zd6=52&Dl5v2W^4GVAixHtkvAq-5+HFlHU#03*uDti2Z>0!|wfNfNryq1~!0!fLy?UfN=l8 z07)py%J4fl=)3)9fj%i}D$ejBztl^ov4E0MXQLp;Xqtt6pv|r=mXiyc3=N+hxkoVf zp(8#ZDgk7jXXs9r)^IFXoFj%!vrKv9LATpH%=eMjlF+7T*7urw<06> z?etXpK;}xDiH55aO3#uq+Dmql*}R9`f{SVa1{IT9H8co+=ui2S^$v%QzKd|2u*)!Z zKoBPi5;)PqLc&X(nR;wex4;B)5R2b&dXidm>cp5>SBcwjfU~LZa?cRhR$zwt3Ex)} zLx%(qD(ki0v|;if{UHKD=hT`}j6g zquSgkY{1YjdKQv+x&Kx5Zb6&-*lGvGQ`%Y1-p{#YvwngFOH+uoavJ8^fM>!`qL(R_ zz=Uq3oRljw)HN_Zn4N2no;DZay_6Q|K9~22ll>=UX5su8{jGptFRD=VZoC@sWOO1( z%*AeNlqwo)^^}a) ziJ!8+v8_~%2El&ni`0#FFs6V4&woUU03R$vhAzz{+>edD5!`f&3JhhIxHq`h^WTRR zfor?o3;+~XHGpK`HTp?5N_|Rhw@1@kIJT`vyQ^5X1bfPq@Eb|JpZP^c{?U>l>UmwhKJq z;NaZg&d%T}&fqK};F+-=ReIhXfY{v8Rwohg53tf%<-Om8p3{3vUWjIM)0z^`Zi?p-duwsfB@3ZoP}70zd8r< z3jw*cp9*5vLP48!Lw<|B%Saebha1-mm=`~35b!vj(_)vy> zR!YXx=xWNt)5gXNs2xUSsC>FK00d>vjwgWdo-RNf`N0)GfLv>5mk=yTK`b|ai=t2& ztDl+2t9*-N*dtxuPEZ)b)Gfu>DAEOGUD?egPMUXfSF!)Qn~QXpDYOlFF?liNy%yey z!~jz&^Fd>w^%{jP3IZeWGx3T8ch9ATh@*7>O9<8L@K*vAe%jY1cByjbiqC?Uz) zT;#>qPI_*3M%{{0_egVQ&`48L)$k_~{V~S00QNj(D_rRoIa9@=LEW+$cr>GtyfAIB z1~Bj%eU((xvs({?s=cqbP3CG50xQL2bn&nOwkGaV+G01Sp<3sm+$^f|iHXV5$s=D( zU5Ycjsn2qBwx+e|G~$O9@p%N=C+!f6$X@3)YDxf+fwboZfmP>qezr^=@BL$1pi!Bq z1T55mFEEcKR##>c_qY&)!4xPp6P+Zadp;GE>3@U;D-1+gojKFSrLvt$*w=GSSnT8Fh$et~K2so4axj$zTRA@(bym@2OF6NA>qe%!RlAm{M>u>CBCsK~ zQ}SMvsTp1?0-knQVE{cvUQ|r5 zR0Uy4(Umn9{@W~Ms5p~H#)k)SEs&x*U>23h0j={uTtj_Yr*C~C)^`2+s%cHuoxqg~>gJ*auRi2ubdC^|V9>s#~xlMSPB z{%OYGM8IDzb4O#9{%XebVSlq?qaM$8(ONNeCg_A9NtVJ67X0ESUOM!gAf$x;9>3bP z+2Gk;t%1wl+&63Xu9}=Z#O>hmMO?jPh?p-=AKd-1C&JLsNY6;m0O*|vstC9VI4WKi z${@&;6%5ROmTJFES=v{T_$=S1D=hE--7KrXuP*`VuLc0Zrzm;PQmm=h^W)@GA zX8nNYc?!>*X&_A@g5^-&TXmecT&r1KxmtO~^#ZO&)wNs<>Z7j8?#jcK7vh9xK;w8R3l%b zW+Qq+KQb?(%h46#_}*S|$nM*-+R(j?Y0SJVnBK#&nV^2Sfy4`MpJZ`c5L*1l?us zmhxnXaF8Yock}(OplKkHqlgJ2J+o`_v~!`DK=-z#_vXje<1taPIWLZ@6L|C^uHp)K z7KeB&XJ5`V3JK@$*~Vv%C17D1Qx43!P<>G@diBiMK1Kz^#>7k&V>@8RLfVf9X~_?; zSfP=n@ef3*SMwg(??L9rbXHiA~D7l$E8?UUEWq+%4c+ zCn7KFgg!2*+`d6kx)_twB!==i4}=$<98(^=Xl$e({L*<$C&BAG$Y2d@+->M>ukK?- zn{Ffyo}R|YvPKew{SxE(p7Nw6pc?cVP{ttjU>gb9(k8d<&#Gz*tu}@ti}?UYY+Zol zt>lII!Y7YD<@>}XV`0VH^1$Jvl#kr7O%;Te*Rb%7?57n&my~xc9oi|0(!8jjw@)$i zD3PoTTTsR8`@ruF^;&TK4R=0>*K3{tmm*EH76tHcG{~V|yN4xseKIXAzY$>Qx$zu( zK_iN(9*3MY205ob7anX75j|1s=sbMAscgIBfXJA+s%Y`jfqTJCD#pfi3S4ckpQ^VV zlsI-WI?^<4%dSs0guirxPQUkhaw<0RL(N1i)B%n)a^Gpw|xid z9Ug33piN%kH{V*gg3!PP@d)bn9sDutef5eqXym$x%%WAQkPQdbw2cyeX!Ksd@wos+ z%yuC1n(&LffO7YD0?+X~kci|m$dIQU!x7eRT?U6qS1-L;-0_#cdvPrNH4Rfkd0GgEL z(GN*iW@N$y6>bR8`*Y*lnMN@$;|H`1ohW>GQ$6SGMkf|4Vdv+I2hhey!<81k%Z z$s8`6A&<3I(h;(jS}+_e)R+p}GtpLP7XGO*Xr@C-R+~;0=IUne&^Q7DYs=#rMhdGH z4BQq%U#42S58HfI<@DVMPEe3-a>vUVn!s5Fb|*T*5vZN?ugqb0tttpc0kB_7ODqs0 zR!TcQdA{ON*ctlJ-!TC=Vnm!m#7H^zc)$VB^3?LU^1=I(`Rc&_}F$sr}&fJ63~~6N-sD3K!3;at4VAr`v0LSG~Qw z-YMnlWZP)TbCvjbB&V}{Vob=6Yj2%Su4V`QqeqWs`M@mC?(;7y+hQB~C%BxuD4=n=Cf&>gE#QpRW4G1Gpre_g46Rn0mqTLSCqEK{gy zsm=NDt_$Z7sbQ68(&5Ipj6jr7ZeaNxD&dlT1`ucO6xUC- z0!DH#wS(p9H=#|Ng3MhU`0kA9B81SDfz=*Xh#NJyM(TPej`_6Iw9;QhkQ^mj;}{j; zZtX$h_t_)y`N_&7()Zi(s%`+dN9F|%U=eNcnE=-#WGspO7G+(0rwXydWV_3vyi<^+ zT_*w^$7C6VrTxQ)0Z}UtMn4jxGI@X7rF&;kw5JTwgD3Pf|G9fO9Mdb%G}5J=MXtW@ zEmoN(S1@U&)*ci6jx6oA+tI#qF17=Ok<{Sfa3H321&ZplF_-nkfg)Oxp^zZ&&8Av( z%A2b!br)JHKK0By^Dw|Dm5F9pR@vIFbmPzP3BlGmf2zB+>t&#;YH@M2bZKa`u{qg4 zY|GK+hD1*;lbQnBI!z~ZvF%g zJ8Qd`uo!2Ca}g^zh8bJJ-{39Ca8!|~CwQvEYN$6A=wy=Zrpl6GF;3zF58pJ_6CyP~ z#&j1R3I^MDot?%|ZDb;7j9-{jEbZVsq+%^lW3ooTm)mxc=RV<$!pGJi!+yG|U%e*^n#-!|e2yVQ9AV zFo9Swqv6!yiS1eJX`RO<8UKr`w#JXv zaF_dz=OA3PxeDdNK}1;D1MfYW!8Eb43eTt)4*)9H9~;lLD^vZwUA96O~Vz-9lvftBt5iGN{Y{m4;!lpCM9X_6pOTT<2 ztjP+QSN%xU4gl+W`CX4MBv$#&W z#e&$d&EwP<1a=NBrW&YB_5+)91<^_w9n6%h+V6fm;% z*(x!R+x_Cii)M*jD$~7VHPL}Ys)A=OA(qC*d+HyJ9kX<84ht*j1{t9AG*t<#^ik5z8);M@R^?}9c18^ zWfwDMG&;b>-UYN)Gvc7>o(3@vtz9at5gM1PYzNFGsg_Go<@PImR@Y*kJ8PMXPp&U& z9o>gHqW)0YTY}&?zSHImJ>ml$S#Y^IPl3xG=>b;?_F_SMS0*N z_WBt60z17SH@!jjEKU3@b6`tz@OE1n>(!t|kb0=Iqmn~bDeytN3*##aRMM^BbQmF? zOK0vqTlhWbD9$!$xjiTxAv!A@A#wv;&-m4fJV7v?d55vzkK!_0bisQdzwb2;il2aE zA|ecny-;5UzEj7!p@yjt|LdkklG0QAu~zY|-M8@lS+C>C1FU=>RR4t3#0Vj6cX1tUasgMa1X;cGd4@yklo)ocD9(-?}l7weS}jwL0SFnzRegLX7vpsPZqo?!C1N zuXdV6qKLP?H9F6L5^#reMqc$-lVO~5qm6iur{cn^FqvZwAYvqYhXt81sFlL#M5P%- zGwM!#85NaBfeqJR!dFpDy!J?lCZ0o`h(3BwrG2H5(}YtXFHTTTWV|zvr8Ru$F`Ldn zQd=h|LrT!DCbN+(N?=@JN|`f4fa8jEO#L$C+-K^}waygze)h^y+1TMbu(htXf;1p2 zRaNX611xjK@EOC6dFNb&zTTt|u0J$UjtLpoVqDZNCm?p#)kuU^c?LCYTwe1@naXSS zyU`T0Wj-;rJ_>m`p|aMVnVK;4lQA8zv2iEvC+tQNScig}MMVc~N>!>LLk@GPX|$r& z>E2r`xbb$##WPyqTNK0lUUAMH(G*{2u_+sB7o!uU-S};*gqbU0>jG+v5xC0x&W*Jq zmtbB~caPKvtF6yJmOiV)y>QOc2kaW?U1G|IiRQ&8e5)rqJ<>?GAorCCb&$!<8DQ$4 zA~Sqo5HCq$BjF{hV`CAI8Za~O5m%DU7LVV_6@(R?r0Q75kPO6tH`h;^m>jM|3tM|S z_&k(f#+$EAZe%{zreZXt5W6~Q7O^=^Fpw(-XSt%xAd8c~3)X~^69$f9%uO?*uOlgE zX%h`L8b8u7`+;9i>NhbW%`mxA}QjP+Yp~f>zhWQ<|9lK8L!auU?

a<_#*O{M9D_OwOlM3sWxoqCB=SIYmcOl`a;JMjL4m_!27&z5(Zx_U;;`?r_pJ@rMwOhs|gA0PoHY z_tRsg2RW(f%n;NbF z!ubs7vu~NI4};rZ%#hp=hWy2F{6ZmOxPo`^i(UWkr_d!{Y*6+VKM_2^Z_h_XafEbK@INgm-vrb?0=azvW!(9drLwKz0XK`5tOflnfufP|#6(=VWT z<(MXv*8^&U?==}+nkgpMYzt(c^W)yNKm*#hr4+_1Pe#-6bawBT=O3_oFeN}7_MAR6 zQcP2aaA+<~$=8H9KJZAug^le4eYHMMTD%B!gnBA`0-fnV!kvPrUONM=xZ;5;WSC8#5ydCe8I^Y0(KDGBnkQS@I%1%>6kCe2Td&MJ zD`-CDf9x&rys5(&DSgx_-w$JiWbYYXRO0owrN}cqHV2PNhpCE_K%KK^;w)|O3v9jV)?L{n+=@Zqe@O6Xrwv2C|d zd!qGE`);WPqtsJWo61o~N=PE?tpX?u(o<74*von((Zs2*OPbOP&*;JjZ=TC+&A&=s zV0?a%7%vPmgPste=Zk6xD_oD_amowAnQ(gnpOn;vd@i~L`$o%gENLVO=kDXxyxE;f z)07vb(&Ozef92UkoH7SBB0sy18{Gn`9OPu1pU zf4j2=L=TYdPn+_h7fkxZKP;hM&)>J3lAhCE2zt;=o>vw_v)NuVJ=5mqMvEeCP(}GH zYH77xL55P-dOboN6fYj?rI(q8i}tYuHMDh;RC7{<*5OZaafFKYc>lfc#5##_W+4Ir zC6E9C5&Zl5OWn=N*!8zAQtXWpUZ9S)x1RVId zAe%D5I5En2*LtPxx;aT0^xp~{*%pm!KPTu%yi`ZDGbdJlaK3AiUQ(9xOi%0)g41J3*e#?N`6*%S z3T2b0pD6cr^60L;eKYsv2p-^GUrbD%k z49dF@-V96(-llsU2#`#iMOGS1QB=~AZ_OX%X9~L;SJl?m($G>*@9=pa{2mx3VYjcq z(GoiJJ8F_dib{MZH5zh;vV4lJdcGL#?gzxC;%Xvq{ju&J-4DVwD8#gC*g9zA=8&SQ z-<5jP4)Z9BC@&Y~r;DQ@3|Tv>zcrS}Z@XVPqcO)x^SLb<)QX*s-9eo@=o`CvnFkPr z)hHKSNsgc<%_AoyB_*V}B___r@2H}V$CXeb)m4`o=)7~0XDrBerKySzo6IiE_gmwa zd`fEq-BrC*K~@?{_4izBzcRO`j{0&$Ak7tfP{@;LC}H$aQGs${$JM{PEvKDj zRS(Us$1f3uGGAZ7(o($9Tsq%IzdQxP#+us7+#*zH+YV_`SW)Y7=lP8*bUvlkeY-#K z(F`n^ z9ht7az1x@(@Rb9{LT%pVuGH21;(m*Vdc^QZvO6L)_H#?9Bj6HM{ZsdFFjd+5^~qpt z;A!ilnWqN{uF(8fPByQp82qMMw&g{{J$b^-x?W@-I4kj*1|V=P%9`kQuR5^fe_4WPEHqeNNkMgip) z&ue&9?1^@(wPZr#f<(;>?Za4>)@&NHv&h^2SGKE*i_@(Z?6d(Lh9S$@W<)NSmKrHF z&FrXS=9RiDiwHyJFs#g?sSc&A{f3qV@*jz+>but4-I`mZDGL$=*|8;>;x*mkRUhJY ziHm(O0Eae)amC;ilEfuO%wn)ATF9X3;rJG0E;)f<`vMm4C%w%aeO@G4DVi%q%|>+()xWU0fQtKf!*JUE5f@y*80?vHP6ga*~4i zXsP$YRg>3pvQ);tW;*SN=GdH|sOqwErRkoV>0RA&5rcwwp=NLyk7oQ$MNgoBk2(~M9z|AjXSC_@$vwAgo^LgHXg zCRLJBTZg8Rif?S%HXZfzejJdeL>5@FxWsr|>;%X)m?t^F+NL1l-J?X|HyvP&;0@lR zMRl|heUTq zZM1Ahqs_WnB0l{t3;sGh3dg3D;vse> zL@#!?TRzIkGtAaK2rH2J}$4bIIC_g<-Wv2A5jbUtPa1+qF8Q3|uKIG7VRry3sQ%g!G$M z!n2z88-5)Pw3`_YUbVv^e{O{EM=P9wrPOTaP{2G}Vs2V6_H^D@yRvH96&{Jg^<;kt z2tOy?d_N*w9>IKxhJ5&hoYr21cBl9yi0NWqH7jd`1Tvk}(p;auw$Hd@+!?9?>h^Aa zBoE8RDqN`P;>!zgWR?4AvzVYz=EF4=4&dJ2Ts`15&sj``H!R+q7@M3rxDSIsKgKLw za|v2_yWO;C$6!o0xCAI1_l_!ugC#PfidUeCxKxJDG0c~qebWZq-uC2xG8qwmkdMq=w3)IKSLhpeUx}<+bkuK!=N$Pp)?VX zTcjjYqBJ#7wewiY^FnZoc6Twc~^jg>0EYlYx#J6wAriR&&AtYO~1lokg4$nw0@|5tUh9; zzOVkss%5>t+@ZRn-dNj^^UIG*i3=DS16qG9Ex~vr>D9fgvq4D}^BnH{D*?`(Gbl*w z8o@pL;w0-#Llcck##!-5Eme`pPiV$L3@n^Vr0kAsZR`_YvK?y_!MKgn)`I*`SKz#- zz3wBj9b49p`UbLmBuM}~48CFRq;j1_0R1%gW6=I<7)y0bhqc?mHb-+xOWq&1F*&sC zFOxVQm)(u_C0}soWv)S%t?p8L6JK>CD<^w)j!M2~Av@nFxqGSS&|C&#VaFp#qzXT2 zVIxDbbDHs)(Gsh;Om)J26IjBIudQha8acsH7!1K7Vrt@O2?PH$1<1HPRI56!UgC;Q zJ1H6J@~i}l{1};XFdy4OhAgpB{*}Pgc07T)45qo1g%chkqXGM+_W3+*$)uz9N3fnm zRF$LmrE1NdtFfR>k((88EXt7{3`&s=JnVbrJ2q^qCGoEr$o2}>q{=F^5d|#giy86c zZVMEd)SpM4v$Y;aPCLcKZ-WKp@l%>*Bt|4#GQbT6YmK=w4c(WinN)H)$vs7y*F|Xd zoSEXIiDg2>H#Bw^ZL-$j+1cGD>d%L?1s9er$GU6y<7t9oKg5!|=&k0P-Y-r*oM&v9 zzvitQW{xdrz#S-I<+7_oe0HEZp!38}qDV9NMrwEY>6~V;fX8>u**dhT^v(OOHDolJK$ z7`$UOVFEL)EoAPgwrJAHflybfo!Sq6GNb8dfwB{j0e&*0 z>HDa4M`l}{DZYn>rwx^{d#jTPMl$Tjhph>-H$ge1nJydZ!I|)Mfwl-6%Zie(BjYYe zr^auIr!`wC*a*=1+E%n=6n^o8;<5_Gt!wht>uefois*oFHZ|Je@P9YbGIU8Wzvk6A zAD-z&={CI*g2ut_ISuliN<}{^n52e1>bWGH;J9Ptg);r#?wYM$b1}2;R=?6c!5XS4 zj{RhBcHCI^n`e;W_nNiDz;`RjHw(nIh< zvNxi|Cs!7wTrNx=E$!a`W(xBT2Ar-Zb%x_6M)npZ8A}WDQnZ~uqm6xTeFmt6Trc`- zg^q(KkF#_R(T`(5)vP+NvKM2Rm!e7}%v6#KNq5ff3_H1AJS5fyLEAl)>vgLrP3xuP zrzA>}P{i5i)WN@lXpi+e`2g=>ro!^HpMdR(4f~_8t43kB4wGxzE1NRH)@ssAL81P_ z5O1Pz`Gdpdz_J&BMB$MzXkfEQ;Hl5)YM?pb@?NnmVnfU0aNe%_jQq1iHM9W-hbH!UMU|7y)}**yRM%FR zMxHPbstzM0(05_}74H&3i6pxCMh0q$(ul-RVGv(vMiK(0 z1f>Kk1HJK^^JDe{@6zB`LWnbG((q5-Q}<7iR#>JAey;&~1bPI935p3;;dcdw21z5d zEgjny93x~6=mQV~;5)|v1ps5fE&v6P08j?Z0pPo+0n9p^b%U>vKj<4#TH#u8THpPE zawT*{a%FUVf3JA1a?Wz@d=6)a=}PK~=E~{{?@H^6y9oCJ(~9c>^n<+-+XKM^@&{NW z$STw+u!}#uCO^@yfS`#o$%(fU2i zM^*bhtVdb&JJL10`RMs@_| z7y=dbA?%U|nAyRw{iUf?iv*`Ynj$8H$bid&7Wt6+Dt5(nX`LgVE1fghVX$G3f|&X+ zcFlCD>+z@oq59Qy5!gjCD~xJc#wWXxQG!pHN##l5D0}FJQV2vZ#dMkEJrsB z@KYiCXIM6mKmz$H*Aj6>mkzhhM~yry!2cV}>LArlpzjCx>5%<|Vp6FaFT-qf=+K;h z0v%vo7F`x(!8QtB#8_k^L4JmRfi^=9KNB*u!2g2cJ3g^MKcnKiu_D+pxk(tX|AH*P zEZ;1DT_0UPU0+@Q4WCJ9unIYdE;){63gxHm(#92mlCSIvXDr zF>vD`onw)_8Ivg{Q~jK{to-lltkd6*=6Bx{9zFw(vy>*u)o7za<*fHFNImLU*J!u zWS;xf&xgk3#4q?OIL!{Dg6L01^!qaGlWg;SWQyk({0*+bk!q*V&HMvd%znbYbm1d` zVKyAbVRp{1@F-#Fhd$`vzRWfW(bo3{u%45@gJ%X*^Ex`&mp}otpRk`MeM8;PN65q^ zAoy3X?6rYz!Y%kP7rC<)YGHc$5oNA&=K-a~d=^KeI*nxU74qem?X8z6XFlh*tzoIR z_$;FTwY}wJYiaz~Jz;)y?iuP6xnb)jvfR4j{d!IHUQXfK_zD(sA%o-)03Po^x64ulMGNtu3)*aojQa&8nKh~?zEASg;DYzmG7+?3< zgMo9OHyT@5T8~*zQW__UV6jVu;LCn3BvXkIQ3y0@C0@r@WJ6eo;>S>;=x0RaNZ96c zlYvfp_wgeb`A5Tay)}_>Av%p}&t=Xf>hT&*>nLs-Rw413V!`mL$SJMH68!Pw>3mA}nVO(al4l}HD z0xynEh*93mZ+3)m9iG%ED6S|Shxc|Q*M%G$1W2ZZ}-2&RQ{3Ye>;i&jH>)C{nVI$yZuXy<-e=_jI#VK z{ZxB?yZw*ae_}5G(EiN_>({8uKlJl`Dc?M^e%1ba4CbG&{2SM=0aSl-mB0BY|H}1C zIMuIYzeb$=NjCTv-SLlPfBJ6!O7?4D$e(03Z#Df-WPc44`IYTgck(~k0%8A|?Z2GM zf2I7@k^4_dUATXy{EJ)npJe~tkbZg_{+52W2#B}f;$PqPKfZ^5Zy3LNAO1m;@5}Y? zB>(J-_-Fb4+3)X9YHXB$E#FV-U%dc-CH~b5?oVPf)PE)Z)hF&((qE57|0FHP`&ZI` zIwbv-_}4R|KZ*AV|CRVJCrH2Y{W?hh$v43AKluJKWXnl{y`BBOIb^ |a|,|b|,|c| + * |:| => ||,|| + * |a:| => |a|,|| + * @author FangYidong + */ +public class ItemList { + private String sp=","; + List items=new ArrayList(); + + + public ItemList(){} + + + public ItemList(String s){ + this.split(s,sp,items); + } + + public ItemList(String s,String sp){ + this.sp=s; + this.split(s,sp,items); + } + + public ItemList(String s,String sp,boolean isMultiToken){ + split(s,sp,items,isMultiToken); + } + + public List getItems(){ + return this.items; + } + + public String[] getArray(){ + return (String[])this.items.toArray(); + } + + public void split(String s,String sp,List append,boolean isMultiToken){ + if(s==null || sp==null) + return; + if(isMultiToken){ + StringTokenizer tokens=new StringTokenizer(s,sp); + while(tokens.hasMoreTokens()){ + append.add(tokens.nextToken().trim()); + } + } + else{ + this.split(s,sp,append); + } + } + + public void split(String s,String sp,List append){ + if(s==null || sp==null) + return; + int pos=0; + int prevPos=0; + do{ + prevPos=pos; + pos=s.indexOf(sp,pos); + if(pos==-1) + break; + append.add(s.substring(prevPos,pos).trim()); + pos+=sp.length(); + }while(pos!=-1); + append.add(s.substring(prevPos).trim()); + } + + public void setSP(String sp){ + this.sp=sp; + } + + public void add(int i,String item){ + if(item==null) + return; + items.add(i,item.trim()); + } + + public void add(String item){ + if(item==null) + return; + items.add(item.trim()); + } + + public void addAll(ItemList list){ + items.addAll(list.items); + } + + public void addAll(String s){ + this.split(s,sp,items); + } + + public void addAll(String s,String sp){ + this.split(s,sp,items); + } + + public void addAll(String s,String sp,boolean isMultiToken){ + this.split(s,sp,items,isMultiToken); + } + + /** + * @param i 0-based + * @return + */ + public String get(int i){ + return (String)items.get(i); + } + + public int size(){ + return items.size(); + } + + public String toString(){ + return toString(sp); + } + + public String toString(String sp){ + StringBuffer sb=new StringBuffer(); + + for(int i=0;i */ -public class JSONArray extends ArrayList { - public String toString(){ - Iterator iter=iterator(); +public class JSONArray extends ArrayList implements List, JSONAware, JSONStreamAware { + private static final long serialVersionUID = 3957988303675231981L; + /** + * Encode a list into JSON text and write it to out. + * If this list is also a JSONStreamAware or a JSONAware, JSONStreamAware and JSONAware specific behaviours will be ignored at this top level. + * + * @see org.json.simple.JSONValue#writeJSONString(Object, Writer) + * + * @param list + * @param out + */ + public static void writeJSONString(List list, Writer out) throws IOException{ + if(list == null){ + out.write("null"); + return; + } + + boolean first = true; + Iterator iter=list.iterator(); + + out.write('['); + while(iter.hasNext()){ + if(first) + first = false; + else + out.write(','); + + Object value=iter.next(); + if(value == null){ + out.write("null"); + continue; + } + + JSONValue.writeJSONString(value, out); + } + out.write(']'); + } + + public void writeJSONString(Writer out) throws IOException{ + writeJSONString(this, out); + } + + /** + * Convert a list to JSON text. The result is a JSON array. + * If this list is also a JSONAware, JSONAware specific behaviours will be omitted at this top level. + * + * @see org.json.simple.JSONValue#toJSONString(Object) + * + * @param list + * @return JSON text, or "null" if list is null. + */ + public static String toJSONString(List list){ + if(list == null) + return "null"; + boolean first = true; StringBuffer sb = new StringBuffer(); + Iterator iter=list.iterator(); + sb.append('['); while(iter.hasNext()){ - if (first) { + if(first) first = false; - } else { + else sb.append(','); - } - JSONObject.escapeValue(iter.next(), sb); + + Object value=iter.next(); + if(value == null){ + sb.append("null"); + continue; + } + sb.append(JSONValue.toJSONString(value)); } sb.append(']'); - return sb.toString(); + return sb.toString(); } + + public String toJSONString(){ + return toJSONString(this); + } + + public String toString() { + return toJSONString(); + } + + } diff --git a/src/org/json/simple/JSONAware.java b/src/org/json/simple/JSONAware.java new file mode 100644 index 0000000..89f1525 --- /dev/null +++ b/src/org/json/simple/JSONAware.java @@ -0,0 +1,12 @@ +package org.json.simple; + +/** + * Beans that support customized output of JSON text shall implement this interface. + * @author FangYidong + */ +public interface JSONAware { + /** + * @return JSON text + */ + String toJSONString(); +} diff --git a/src/org/json/simple/JSONObject.java b/src/org/json/simple/JSONObject.java index d6fe95d..d4401e1 100644 --- a/src/org/json/simple/JSONObject.java +++ b/src/org/json/simple/JSONObject.java @@ -4,121 +4,126 @@ */ package org.json.simple; +import java.io.IOException; +import java.io.Writer; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** + * A JSON object. Key value pairs are unordered. JSONObject supports java.util.Map interface. + * * @author FangYidong */ -public class JSONObject extends HashMap{ - - public String toString(){ - Iterator iter=entrySet().iterator(); +public class JSONObject extends HashMap implements Map, JSONAware, JSONStreamAware{ + private static final long serialVersionUID = -503443796854799292L; - boolean first = true; + /** + * Encode a map into JSON text and write it to out. + * If this map is also a JSONAware or JSONStreamAware, JSONAware or JSONStreamAware specific behaviours will be ignored at this top level. + * + * @see org.json.simple.JSONValue#writeJSONString(Object, Writer) + * + * @param map + * @param out + */ + public static void writeJSONString(Map map, Writer out) throws IOException { + if(map == null){ + out.write("null"); + return; + } + + boolean first = true; + Iterator iter=map.entrySet().iterator(); + + out.write('{'); + while(iter.hasNext()){ + if(first) + first = false; + else + out.write(','); + Map.Entry entry=(Map.Entry)iter.next(); + out.write('\"'); + out.write(escape(String.valueOf(entry.getKey()))); + out.write('\"'); + out.write(':'); + JSONValue.writeJSONString(entry.getValue(), out); + } + out.write('}'); + } + + public void writeJSONString(Writer out) throws IOException{ + writeJSONString(this, out); + } + + /** + * Convert a map to JSON text. The result is a JSON object. + * If this map is also a JSONAware, JSONAware specific behaviours will be omitted at this top level. + * + * @see org.json.simple.JSONValue#toJSONString(Object) + * + * @param map + * @return JSON text, or "null" if map is null. + */ + public static String toJSONString(Map map){ + if(map == null) + return "null"; + StringBuffer sb = new StringBuffer(); + boolean first = true; + Iterator iter=map.entrySet().iterator(); + sb.append('{'); while(iter.hasNext()){ - if (first) { + if(first) first = false; - } else { + else sb.append(','); - } + Map.Entry entry=(Map.Entry)iter.next(); - toString(entry.getKey().toString(), entry.getValue(), sb); + toJSONString(String.valueOf(entry.getKey()),entry.getValue(), sb); } sb.append('}'); + return sb.toString(); + } + + public String toJSONString(){ + return toJSONString(this); + } + + private static String toJSONString(String key,Object value, StringBuffer sb){ + sb.append('\"'); + if(key == null) + sb.append("null"); + else + JSONValue.escape(key, sb); + sb.append('\"').append(':'); + + sb.append(JSONValue.toJSONString(value)); + + return sb.toString(); + } + + public String toString(){ + return toJSONString(); + } + + public static String toString(String key,Object value){ + StringBuffer sb = new StringBuffer(); + toJSONString(key, value, sb); return sb.toString(); } - public static String toString(String key, Object value){ - return toString(key, value, new StringBuffer()).toString(); - } - - private static StringBuffer toString(String key, Object value, StringBuffer sb){ - sb.append('\"'); - escape(key, sb); - sb.append("\":"); - escapeValue(value, sb); - return sb; - } - - // Package-protected for JSONArray - static void escapeValue(Object value, StringBuffer sb) { - if (value instanceof String){ - sb.append('\"'); - escape((String) value, sb); - sb.append('\"'); - } else { - sb.append(String.valueOf(value)); - } - } - /** - * " => \" , \ => \\ + * Escape quotes, \, /, \r, \n, \b, \f, \t and other control characters (U+0000 through U+001F). + * It's the same as JSONValue.escape() only for compatibility here. + * + * @see org.json.simple.JSONValue#escape(String) + * * @param s * @return */ public static String escape(String s){ - if(s==null) - return null; - StringBuffer sb = new StringBuffer(); - escape(s, sb); - return sb.toString(); - } - - // Package-protected for JSONArray - static void escape(String s, StringBuffer sb) { - for(int i=0;i='\u0000' && ch<='\u001F'){ - String ss=Integer.toHexString(ch); - sb.append("\\u"); - for(int k=0;k<4-ss.length();k++){ - sb.append('0'); - } - sb.append(ss.toUpperCase()); - } - else{ - sb.append(ch); - } - } - }//for + return JSONValue.escape(s); } } diff --git a/src/org/json/simple/JSONStreamAware.java b/src/org/json/simple/JSONStreamAware.java new file mode 100644 index 0000000..c2287c4 --- /dev/null +++ b/src/org/json/simple/JSONStreamAware.java @@ -0,0 +1,15 @@ +package org.json.simple; + +import java.io.IOException; +import java.io.Writer; + +/** + * Beans that support customized output of JSON text to a writer shall implement this interface. + * @author FangYidong + */ +public interface JSONStreamAware { + /** + * write JSON string to out. + */ + void writeJSONString(Writer out) throws IOException; +} diff --git a/src/org/json/simple/JSONValue.java b/src/org/json/simple/JSONValue.java index 662a1b9..18bde72 100644 --- a/src/org/json/simple/JSONValue.java +++ b/src/org/json/simple/JSONValue.java @@ -4,10 +4,15 @@ */ package org.json.simple; +import java.io.IOException; import java.io.Reader; import java.io.StringReader; +import java.io.Writer; +import java.util.List; +import java.util.Map; import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; /** @@ -15,9 +20,21 @@ import org.json.simple.parser.JSONParser; */ public class JSONValue { /** - * parse into java object from input source. + * Parse JSON text into java object from the input source. + * Please use parseWithException() if you don't want to ignore the exception. + * + * @see org.json.simple.parser.JSONParser#parse(Reader) + * @see #parseWithException(Reader) + * * @param in - * @return instance of : JSONObject,JSONArray,String,Boolean,Long,Double or null + * @return Instance of the following: + * org.json.simple.JSONObject, + * org.json.simple.JSONArray, + * java.lang.String, + * java.lang.Number, + * java.lang.Boolean, + * null + * */ public static Object parse(Reader in){ try{ @@ -33,4 +50,231 @@ public class JSONValue { StringReader in=new StringReader(s); return parse(in); } + + /** + * Parse JSON text into java object from the input source. + * + * @see org.json.simple.parser.JSONParser + * + * @param in + * @return Instance of the following: + * org.json.simple.JSONObject, + * org.json.simple.JSONArray, + * java.lang.String, + * java.lang.Number, + * java.lang.Boolean, + * null + * + * @throws IOException + * @throws ParseException + */ + public static Object parseWithException(Reader in) throws IOException, ParseException{ + JSONParser parser=new JSONParser(); + return parser.parse(in); + } + + public static Object parseWithException(String s) throws ParseException{ + JSONParser parser=new JSONParser(); + return parser.parse(s); + } + + /** + * Encode an object into JSON text and write it to out. + *

+ * If this object is a Map or a List, and it's also a JSONStreamAware or a JSONAware, JSONStreamAware or JSONAware will be considered firstly. + *

+ * DO NOT call this method from writeJSONString(Writer) of a class that implements both JSONStreamAware and (Map or List) with + * "this" as the first parameter, use JSONObject.writeJSONString(Map, Writer) or JSONArray.writeJSONString(List, Writer) instead. + * + * @see org.json.simple.JSONObject#writeJSONString(Map, Writer) + * @see org.json.simple.JSONArray#writeJSONString(List, Writer) + * + * @param value + * @param writer + */ + public static void writeJSONString(Object value, Writer out) throws IOException { + if(value == null){ + out.write("null"); + return; + } + + if(value instanceof String){ + out.write('\"'); + out.write(escape((String)value)); + out.write('\"'); + return; + } + + if(value instanceof Double){ + if(((Double)value).isInfinite() || ((Double)value).isNaN()) + out.write("null"); + else + out.write(value.toString()); + return; + } + + if(value instanceof Float){ + if(((Float)value).isInfinite() || ((Float)value).isNaN()) + out.write("null"); + else + out.write(value.toString()); + return; + } + + if(value instanceof Number){ + out.write(value.toString()); + return; + } + + if(value instanceof Boolean){ + out.write(value.toString()); + return; + } + + if((value instanceof JSONStreamAware)){ + ((JSONStreamAware)value).writeJSONString(out); + return; + } + + if((value instanceof JSONAware)){ + out.write(((JSONAware)value).toJSONString()); + return; + } + + if(value instanceof Map){ + JSONObject.writeJSONString((Map)value, out); + return; + } + + if(value instanceof List){ + JSONArray.writeJSONString((List)value, out); + return; + } + + out.write(value.toString()); + } + + /** + * Convert an object to JSON text. + *

+ * If this object is a Map or a List, and it's also a JSONAware, JSONAware will be considered firstly. + *

+ * DO NOT call this method from toJSONString() of a class that implements both JSONAware and Map or List with + * "this" as the parameter, use JSONObject.toJSONString(Map) or JSONArray.toJSONString(List) instead. + * + * @see org.json.simple.JSONObject#toJSONString(Map) + * @see org.json.simple.JSONArray#toJSONString(List) + * + * @param value + * @return JSON text, or "null" if value is null or it's an NaN or an INF number. + */ + public static String toJSONString(Object value){ + if(value == null) + return "null"; + + if(value instanceof String) + return "\""+escape((String)value)+"\""; + + if(value instanceof Double){ + if(((Double)value).isInfinite() || ((Double)value).isNaN()) + return "null"; + else + return value.toString(); + } + + if(value instanceof Float){ + if(((Float)value).isInfinite() || ((Float)value).isNaN()) + return "null"; + else + return value.toString(); + } + + if(value instanceof Number) + return value.toString(); + + if(value instanceof Boolean) + return value.toString(); + + if((value instanceof JSONAware)) + return ((JSONAware)value).toJSONString(); + + if(value instanceof Map) + return JSONObject.toJSONString((Map)value); + + if(value instanceof List) + return JSONArray.toJSONString((List)value); + + return value.toString(); + } + + /** + * Escape quotes, \, /, \r, \n, \b, \f, \t and other control characters (U+0000 through U+001F). + * @param s + * @return + */ + public static String escape(String s){ + if(s==null) + return null; + StringBuffer sb = new StringBuffer(); + escape(s, sb); + return sb.toString(); + } + + /** + * @param s - Must not be null. + * @param sb + */ + static void escape(String s, StringBuffer sb) { + for(int i=0;i='\u0000' && ch<='\u001F'){ + String ss=Integer.toHexString(ch); + sb.append("\\u"); + for(int k=0;k<4-ss.length();k++){ + sb.append('0'); + } + sb.append(ss.toUpperCase()); + } + else{ + sb.append(ch); + } + } + }//for + } + } diff --git a/src/org/json/simple/parser/ContainerFactory.java b/src/org/json/simple/parser/ContainerFactory.java new file mode 100644 index 0000000..366ac4d --- /dev/null +++ b/src/org/json/simple/parser/ContainerFactory.java @@ -0,0 +1,23 @@ +package org.json.simple.parser; + +import java.util.List; +import java.util.Map; + +/** + * Container factory for creating containers for JSON object and JSON array. + * + * @see org.json.simple.parser.JSONParser#parse(java.io.Reader, ContainerFactory) + * + * @author FangYidong + */ +public interface ContainerFactory { + /** + * @return A Map instance to store JSON object, or null if you want to use org.json.simple.JSONObject. + */ + Map createObjectContainer(); + + /** + * @return A List instance to store JSON array, or null if you want to use org.json.simple.JSONArray. + */ + List creatArrayContainer(); +} diff --git a/src/org/json/simple/parser/ContentHandler.java b/src/org/json/simple/parser/ContentHandler.java new file mode 100644 index 0000000..a898ba0 --- /dev/null +++ b/src/org/json/simple/parser/ContentHandler.java @@ -0,0 +1,99 @@ +package org.json.simple.parser; + +import java.io.IOException; + +/** + * A simplified and stoppable SAX-like content handler for stream processing of JSON text. + * + * @see org.xml.sax.ContentHandler + * + * @author FangYidong + */ +public interface ContentHandler { + /** + * Receive notification of the beginning of JSON processing. + * + * User can throw a ParseException with errorType ParseException.INFO_USER_STOP to let the parser stop. + * + * @throws ParseException + * JSONParser will stop and throw the same exception to the caller when receiving this exception. + */ + void startJSON() throws ParseException, IOException; + + /** + * Receive notification of the end of JSON processing. + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + */ + void endJSON() throws ParseException, IOException; + + /** + * Receive notification of the beginning of a JSON object. + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + */ + boolean startObject() throws ParseException, IOException; + + /** + * Receive notification of the end of a JSON object. + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + */ + boolean endObject() throws ParseException, IOException; + + /** + * Receive notification of the beginning of a JSON object key value pair. + * + * @param key - Key of a JSON object key value pair. + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + */ + boolean startObjectEntry(String key) throws ParseException, IOException; + + /** + * Receive notification of the end of the value of previous object entry. + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + */ + boolean endObjectEntry() throws ParseException, IOException; + + /** + * Receive notification of the beginning of a JSON array. + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + */ + boolean startArray() throws ParseException, IOException; + + /** + * Receive notification of the end of a JSON array. + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + */ + boolean endArray() throws ParseException, IOException; + + /** + * Receive notification of the JSON primitive values: + * java.lang.String, + * java.lang.Number, + * java.lang.Boolean + * null + * + * @param value - Instance of the following: + * java.lang.String, + * java.lang.Number, + * java.lang.Boolean + * null + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + */ + boolean primitive(Object value) throws ParseException, IOException; + +} diff --git a/src/org/json/simple/parser/JSONParser.java b/src/org/json/simple/parser/JSONParser.java index 392adfc..9acaa37 100644 --- a/src/org/json/simple/parser/JSONParser.java +++ b/src/org/json/simple/parser/JSONParser.java @@ -4,14 +4,20 @@ */ package org.json.simple.parser; +import java.io.IOException; import java.io.Reader; +import java.io.StringReader; import java.util.LinkedList; +import java.util.List; +import java.util.Map; import org.json.simple.JSONArray; import org.json.simple.JSONObject; /** + * Parser for JSON text. Please note that JSONParser is NOT thread-safe. + * * @author FangYidong */ public class JSONParser { @@ -20,10 +26,11 @@ public class JSONParser { public static final int S_IN_OBJECT=2; public static final int S_IN_ARRAY=3; public static final int S_PASSED_PAIR_KEY=4; + public static final int S_IN_PAIR_VALUE=5; + public static final int S_END=6; public static final int S_IN_ERROR=-1; - private LinkedList statusStack = new LinkedList(); - private LinkedList valueStack = new LinkedList(); + private LinkedList handlerStatusStack; private Yylex lexer = new Yylex((Reader)null); private Yytoken token = null; private int status = S_INIT; @@ -35,22 +42,80 @@ public class JSONParser { return status.intValue(); } - private void reset(Reader in) throws Exception{ - statusStack.clear(); - valueStack.clear(); + /** + * Reset the parser to the initial state without resetting the underlying reader. + * + */ + public void reset(){ + token = null; + status = S_INIT; + handlerStatusStack = null; + } + + /** + * Reset the parser to the initial state with a new character reader. + * + * @param in - The new character reader. + * @throws IOException + * @throws ParseException + */ + public void reset(Reader in){ lexer.yyreset(in); - token = null; - status = S_INIT; + reset(); } - public Object parse(Reader in) throws Exception{ + /** + * @return The position of the beginning of the current token. + */ + public int getPosition(){ + return lexer.getPosition(); + } + + public Object parse(String s) throws ParseException{ + return parse(s, (ContainerFactory)null); + } + + public Object parse(String s, ContainerFactory containerFactory) throws ParseException{ + StringReader in=new StringReader(s); + try{ + return parse(in, containerFactory); + } + catch(IOException ie){ + /* + * Actually it will never happen. + */ + throw new ParseException(-1, ParseException.ERROR_UNEXPECTED_EXCEPTION, ie); + } + } + + public Object parse(Reader in) throws IOException, ParseException{ + return parse(in, (ContainerFactory)null); + } + + /** + * Parse JSON text into java object from the input source. + * + * @param in + * @param containerFactory - Use this factory to createyour own JSON object and JSON array containers. + * @return Instance of the following: + * org.json.simple.JSONObject, + * org.json.simple.JSONArray, + * java.lang.String, + * java.lang.Number, + * java.lang.Boolean, + * null + * + * @throws IOException + * @throws ParseException + */ + public Object parse(Reader in, ContainerFactory containerFactory) throws IOException, ParseException{ reset(in); - + LinkedList statusStack = new LinkedList(); + LinkedList valueStack = new LinkedList(); + try{ do{ - token=lexer.yylex(); - if(token==null) - token=new Yytoken(Yytoken.TYPE_EOF,null); + nextToken(); switch(status){ case S_INIT: switch(token.type){ @@ -62,12 +127,12 @@ public class JSONParser { case Yytoken.TYPE_LEFT_BRACE: status=S_IN_OBJECT; statusStack.addFirst(new Integer(status)); - valueStack.addFirst(new JSONObject()); + valueStack.addFirst(createObjectContainer(containerFactory)); break; case Yytoken.TYPE_LEFT_SQUARE: status=S_IN_ARRAY; statusStack.addFirst(new Integer(status)); - valueStack.addFirst(new JSONArray()); + valueStack.addFirst(createArrayContainer(containerFactory)); break; default: status=S_IN_ERROR; @@ -78,7 +143,7 @@ public class JSONParser { if(token.type==Yytoken.TYPE_EOF) return valueStack.removeFirst(); else - return null; + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); case S_IN_OBJECT: switch(token.type){ @@ -118,15 +183,15 @@ public class JSONParser { case Yytoken.TYPE_VALUE: statusStack.removeFirst(); String key=(String)valueStack.removeFirst(); - JSONObject parent=(JSONObject)valueStack.getFirst(); + Map parent=(Map)valueStack.getFirst(); parent.put(key,token.value); status=peekStatus(statusStack); break; case Yytoken.TYPE_LEFT_SQUARE: statusStack.removeFirst(); key=(String)valueStack.removeFirst(); - parent=(JSONObject)valueStack.getFirst(); - JSONArray newArray=new JSONArray(); + parent=(Map)valueStack.getFirst(); + List newArray=createArrayContainer(containerFactory); parent.put(key,newArray); status=S_IN_ARRAY; statusStack.addFirst(new Integer(status)); @@ -135,8 +200,8 @@ public class JSONParser { case Yytoken.TYPE_LEFT_BRACE: statusStack.removeFirst(); key=(String)valueStack.removeFirst(); - parent=(JSONObject)valueStack.getFirst(); - JSONObject newObject=new JSONObject(); + parent=(Map)valueStack.getFirst(); + Map newObject=createObjectContainer(containerFactory); parent.put(key,newObject); status=S_IN_OBJECT; statusStack.addFirst(new Integer(status)); @@ -152,7 +217,7 @@ public class JSONParser { case Yytoken.TYPE_COMMA: break; case Yytoken.TYPE_VALUE: - JSONArray val=(JSONArray)valueStack.getFirst(); + List val=(List)valueStack.getFirst(); val.add(token.value); break; case Yytoken.TYPE_RIGHT_SQUARE: @@ -166,16 +231,16 @@ public class JSONParser { } break; case Yytoken.TYPE_LEFT_BRACE: - val=(JSONArray)valueStack.getFirst(); - JSONObject newObject=new JSONObject(); + val=(List)valueStack.getFirst(); + Map newObject=createObjectContainer(containerFactory); val.add(newObject); status=S_IN_OBJECT; statusStack.addFirst(new Integer(status)); valueStack.addFirst(newObject); break; case Yytoken.TYPE_LEFT_SQUARE: - val=(JSONArray)valueStack.getFirst(); - JSONArray newArray=new JSONArray(); + val=(List)valueStack.getFirst(); + List newArray=createArrayContainer(containerFactory); val.add(newArray); status=S_IN_ARRAY; statusStack.addFirst(new Integer(status)); @@ -186,15 +251,283 @@ public class JSONParser { }//inner switch break; case S_IN_ERROR: - return null; + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); }//switch - if(status==S_IN_ERROR) - return null; + if(status==S_IN_ERROR){ + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); + } }while(token.type!=Yytoken.TYPE_EOF); } - catch(Exception e){ + catch(IOException ie){ + throw ie; + } + + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); + } + + private void nextToken() throws ParseException, IOException{ + token = lexer.yylex(); + if(token == null) + token = new Yytoken(Yytoken.TYPE_EOF, null); + } + + private Map createObjectContainer(ContainerFactory containerFactory){ + if(containerFactory == null) + return new JSONObject(); + Map m = containerFactory.createObjectContainer(); + + if(m == null) + return new JSONObject(); + return m; + } + + private List createArrayContainer(ContainerFactory containerFactory){ + if(containerFactory == null) + return new JSONArray(); + List l = containerFactory.creatArrayContainer(); + + if(l == null) + return new JSONArray(); + return l; + } + + public void parse(String s, ContentHandler contentHandler) throws ParseException{ + parse(s, contentHandler, false); + } + + public void parse(String s, ContentHandler contentHandler, boolean isResume) throws ParseException{ + StringReader in=new StringReader(s); + try{ + parse(in, contentHandler, isResume); + } + catch(IOException ie){ + /* + * Actually it will never happen. + */ + throw new ParseException(-1, ParseException.ERROR_UNEXPECTED_EXCEPTION, ie); + } + } + + public void parse(Reader in, ContentHandler contentHandler) throws IOException, ParseException{ + parse(in, contentHandler, false); + } + + /** + * Stream processing of JSON text. + * + * @see ContentHandler + * + * @param in + * @param contentHandler + * @param isResume - Indicates if it continues previous parsing operation. + * If set to true, resume parsing the old stream, and parameter 'in' will be ignored. + * If this method is called for the first time in this instance, isResume will be ignored. + * + * @throws IOException + * @throws ParseException + */ + public void parse(Reader in, ContentHandler contentHandler, boolean isResume) throws IOException, ParseException{ + if(!isResume){ + reset(in); + handlerStatusStack = new LinkedList(); + } + else{ + if(handlerStatusStack == null){ + isResume = false; + reset(in); + handlerStatusStack = new LinkedList(); + } + } + + LinkedList statusStack = handlerStatusStack; + + try{ + do{ + switch(status){ + case S_INIT: + contentHandler.startJSON(); + nextToken(); + switch(token.type){ + case Yytoken.TYPE_VALUE: + status=S_IN_FINISHED_VALUE; + statusStack.addFirst(new Integer(status)); + if(!contentHandler.primitive(token.value)) + return; + break; + case Yytoken.TYPE_LEFT_BRACE: + status=S_IN_OBJECT; + statusStack.addFirst(new Integer(status)); + if(!contentHandler.startObject()) + return; + break; + case Yytoken.TYPE_LEFT_SQUARE: + status=S_IN_ARRAY; + statusStack.addFirst(new Integer(status)); + if(!contentHandler.startArray()) + return; + break; + default: + status=S_IN_ERROR; + }//inner switch + break; + + case S_IN_FINISHED_VALUE: + nextToken(); + if(token.type==Yytoken.TYPE_EOF){ + contentHandler.endJSON(); + status = S_END; + return; + } + else{ + status = S_IN_ERROR; + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); + } + + case S_IN_OBJECT: + nextToken(); + switch(token.type){ + case Yytoken.TYPE_COMMA: + break; + case Yytoken.TYPE_VALUE: + if(token.value instanceof String){ + String key=(String)token.value; + status=S_PASSED_PAIR_KEY; + statusStack.addFirst(new Integer(status)); + if(!contentHandler.startObjectEntry(key)) + return; + } + else{ + status=S_IN_ERROR; + } + break; + case Yytoken.TYPE_RIGHT_BRACE: + if(statusStack.size()>1){ + statusStack.removeFirst(); + status=peekStatus(statusStack); + } + else{ + status=S_IN_FINISHED_VALUE; + } + if(!contentHandler.endObject()) + return; + break; + default: + status=S_IN_ERROR; + break; + }//inner switch + break; + + case S_PASSED_PAIR_KEY: + nextToken(); + switch(token.type){ + case Yytoken.TYPE_COLON: + break; + case Yytoken.TYPE_VALUE: + statusStack.removeFirst(); + status=peekStatus(statusStack); + if(!contentHandler.primitive(token.value)) + return; + if(!contentHandler.endObjectEntry()) + return; + break; + case Yytoken.TYPE_LEFT_SQUARE: + statusStack.removeFirst(); + statusStack.addFirst(new Integer(S_IN_PAIR_VALUE)); + status=S_IN_ARRAY; + statusStack.addFirst(new Integer(status)); + if(!contentHandler.startArray()) + return; + break; + case Yytoken.TYPE_LEFT_BRACE: + statusStack.removeFirst(); + statusStack.addFirst(new Integer(S_IN_PAIR_VALUE)); + status=S_IN_OBJECT; + statusStack.addFirst(new Integer(status)); + if(!contentHandler.startObject()) + return; + break; + default: + status=S_IN_ERROR; + } + break; + + case S_IN_PAIR_VALUE: + /* + * S_IN_PAIR_VALUE is just a marker to indicate the end of an object entry, it doesn't proccess any token, + * therefore delay consuming token until next round. + */ + statusStack.removeFirst(); + status = peekStatus(statusStack); + if(!contentHandler.endObjectEntry()) + return; + break; + + case S_IN_ARRAY: + nextToken(); + switch(token.type){ + case Yytoken.TYPE_COMMA: + break; + case Yytoken.TYPE_VALUE: + if(!contentHandler.primitive(token.value)) + return; + break; + case Yytoken.TYPE_RIGHT_SQUARE: + if(statusStack.size()>1){ + statusStack.removeFirst(); + status=peekStatus(statusStack); + } + else{ + status=S_IN_FINISHED_VALUE; + } + if(!contentHandler.endArray()) + return; + break; + case Yytoken.TYPE_LEFT_BRACE: + status=S_IN_OBJECT; + statusStack.addFirst(new Integer(status)); + if(!contentHandler.startObject()) + return; + break; + case Yytoken.TYPE_LEFT_SQUARE: + status=S_IN_ARRAY; + statusStack.addFirst(new Integer(status)); + if(!contentHandler.startArray()) + return; + break; + default: + status=S_IN_ERROR; + }//inner switch + break; + + case S_END: + return; + + case S_IN_ERROR: + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); + }//switch + if(status==S_IN_ERROR){ + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); + } + }while(token.type!=Yytoken.TYPE_EOF); + } + catch(IOException ie){ + status = S_IN_ERROR; + throw ie; + } + catch(ParseException pe){ + status = S_IN_ERROR; + throw pe; + } + catch(RuntimeException re){ + status = S_IN_ERROR; + throw re; + } + catch(Error e){ + status = S_IN_ERROR; throw e; } - return null; + + status = S_IN_ERROR; + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); } } diff --git a/src/org/json/simple/parser/ParseException.java b/src/org/json/simple/parser/ParseException.java new file mode 100644 index 0000000..f497cd9 --- /dev/null +++ b/src/org/json/simple/parser/ParseException.java @@ -0,0 +1,90 @@ +package org.json.simple.parser; + +/** + * ParseException explains why and where the error occurs in source JSON text. + * + * @author FangYidong + * + */ +public class ParseException extends Exception { + private static final long serialVersionUID = -7880698968187728548L; + + public static final int ERROR_UNEXPECTED_CHAR = 0; + public static final int ERROR_UNEXPECTED_TOKEN = 1; + public static final int ERROR_UNEXPECTED_EXCEPTION = 2; + + private int errorType; + private Object unexpectedObject; + private int position; + + public ParseException(int errorType){ + this(-1, errorType, null); + } + + public ParseException(int errorType, Object unexpectedObject){ + this(-1, errorType, unexpectedObject); + } + + public ParseException(int position, int errorType, Object unexpectedObject){ + this.position = position; + this.errorType = errorType; + this.unexpectedObject = unexpectedObject; + } + + public int getErrorType() { + return errorType; + } + + public void setErrorType(int errorType) { + this.errorType = errorType; + } + + /** + * @see org.json.simple.parser.JSONParser#getPosition() + * + * @return The character position (starting with 0) of the input where the error occurs. + */ + public int getPosition() { + return position; + } + + public void setPosition(int position) { + this.position = position; + } + + /** + * @see org.json.simple.parser.Yytoken + * + * @return One of the following base on the value of errorType: + * ERROR_UNEXPECTED_CHAR java.lang.Character + * ERROR_UNEXPECTED_TOKEN org.json.simple.parser.Yytoken + * ERROR_UNEXPECTED_EXCEPTION java.lang.Exception + */ + public Object getUnexpectedObject() { + return unexpectedObject; + } + + public void setUnexpectedObject(Object unexpectedObject) { + this.unexpectedObject = unexpectedObject; + } + + public String toString(){ + StringBuffer sb = new StringBuffer(); + + switch(errorType){ + case ERROR_UNEXPECTED_CHAR: + sb.append("Unexpected character (").append(unexpectedObject).append(") at position ").append(position).append("."); + break; + case ERROR_UNEXPECTED_TOKEN: + sb.append("Unexpected token ").append(unexpectedObject).append(" at position ").append(position).append("."); + break; + case ERROR_UNEXPECTED_EXCEPTION: + sb.append("Unexpected exception at position ").append(position).append(": ").append(unexpectedObject); + break; + default: + sb.append("Unkown error at position ").append(position).append("."); + break; + } + return sb.toString(); + } +} diff --git a/src/org/json/simple/parser/Yylex.java b/src/org/json/simple/parser/Yylex.java index a9d103d..42ce508 100644 --- a/src/org/json/simple/parser/Yylex.java +++ b/src/org/json/simple/parser/Yylex.java @@ -28,11 +28,11 @@ class Yylex { * Translates characters to character classes */ private static final String ZZ_CMAP_PACKED = - "\11\0\2\7\2\0\1\7\22\0\1\7\1\0\1\11\10\0\1\6"+ - "\1\31\1\2\1\4\1\12\12\3\1\32\6\0\4\1\1\5\1\1"+ - "\24\0\1\27\1\10\1\30\3\0\1\22\1\13\2\1\1\21\1\14"+ - "\5\0\1\23\1\0\1\15\3\0\1\16\1\24\1\17\1\20\5\0"+ - "\1\25\1\0\1\26\uff82\0"; + "\11\0\1\7\1\7\2\0\1\7\22\0\1\7\1\0\1\11\10\0"+ + "\1\6\1\31\1\2\1\4\1\12\12\3\1\32\6\0\4\1\1\5"+ + "\1\1\24\0\1\27\1\10\1\30\3\0\1\22\1\13\2\1\1\21"+ + "\1\14\5\0\1\23\1\0\1\15\3\0\1\16\1\24\1\17\1\20"+ + "\5\0\1\25\1\0\1\26\uff82\0"; /** * Translates characters to character classes @@ -45,13 +45,14 @@ class Yylex { private static final int [] ZZ_ACTION = zzUnpackAction(); private static final String ZZ_ACTION_PACKED_0 = - "\3\0\1\1\1\2\1\3\3\0\1\4\1\5\1\6"+ - "\1\7\1\10\1\11\1\12\1\0\1\13\5\0\1\14"+ - "\1\15\1\16\1\17\1\20\1\21\1\22\1\23\1\0"+ - "\1\24\1\0\1\24\4\0\1\25\1\26\2\0\1\27"; + "\2\0\2\1\1\2\1\3\1\4\3\1\1\5\1\6"+ + "\1\7\1\10\1\11\1\12\1\13\1\14\1\15\5\0"+ + "\1\14\1\16\1\17\1\20\1\21\1\22\1\23\1\24"+ + "\1\0\1\25\1\0\1\25\4\0\1\26\1\27\2\0"+ + "\1\30"; private static int [] zzUnpackAction() { - int [] result = new int[44]; + int [] result = new int[45]; int offset = 0; offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result); return result; @@ -70,6 +71,111 @@ class Yylex { } + /** + * Translates a state to a row index in the transition table + */ + private static final int [] ZZ_ROWMAP = zzUnpackRowMap(); + + private static final String ZZ_ROWMAP_PACKED_0 = + "\0\0\0\33\0\66\0\121\0\154\0\207\0\66\0\242"+ + "\0\275\0\330\0\66\0\66\0\66\0\66\0\66\0\66"+ + "\0\363\0\u010e\0\66\0\u0129\0\u0144\0\u015f\0\u017a\0\u0195"+ + "\0\66\0\66\0\66\0\66\0\66\0\66\0\66\0\66"+ + "\0\u01b0\0\u01cb\0\u01e6\0\u01e6\0\u0201\0\u021c\0\u0237\0\u0252"+ + "\0\66\0\66\0\u026d\0\u0288\0\66"; + + private static int [] zzUnpackRowMap() { + int [] result = new int[45]; + int offset = 0; + offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackRowMap(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int high = packed.charAt(i++) << 16; + result[j++] = high | packed.charAt(i++); + } + return j; + } + + /** + * The transition table of the DFA + */ + private static final int ZZ_TRANS [] = { + 2, 2, 3, 4, 2, 2, 2, 5, 2, 6, + 2, 2, 7, 8, 2, 9, 2, 2, 2, 2, + 2, 10, 11, 12, 13, 14, 15, 16, 16, 16, + 16, 16, 16, 16, 16, 17, 18, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 4, 19, 20, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 20, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 5, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 21, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 22, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 23, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 16, 16, 16, 16, 16, 16, 16, + 16, -1, -1, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + -1, -1, -1, -1, -1, -1, -1, -1, 24, 25, + 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 33, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 34, 35, -1, -1, + 34, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 36, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 37, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 38, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 39, -1, 39, -1, 39, -1, -1, + -1, -1, -1, 39, 39, -1, -1, -1, -1, 39, + 39, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 33, -1, 20, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 20, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 35, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 38, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 40, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 41, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 42, -1, 42, -1, 42, + -1, -1, -1, -1, -1, 42, 42, -1, -1, -1, + -1, 42, 42, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 43, -1, 43, -1, 43, -1, -1, -1, + -1, -1, 43, 43, -1, -1, -1, -1, 43, 43, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 44, + -1, 44, -1, 44, -1, -1, -1, -1, -1, 44, + 44, -1, -1, -1, -1, 44, 44, -1, -1, -1, + -1, -1, -1, -1, -1, + }; + /* error codes */ private static final int ZZ_UNKNOWN_ERROR = 0; private static final int ZZ_NO_MATCH = 1; @@ -82,6 +188,35 @@ class Yylex { "Error: pushback value was too large" }; + /** + * ZZ_ATTRIBUTE[aState] contains the attributes of state aState + */ + private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute(); + + private static final String ZZ_ATTRIBUTE_PACKED_0 = + "\2\0\1\11\3\1\1\11\3\1\6\11\2\1\1\11"+ + "\5\0\10\11\1\0\1\1\1\0\1\1\4\0\2\11"+ + "\2\0\1\11"; + + private static int [] zzUnpackAttribute() { + int [] result = new int[45]; + int offset = 0; + offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackAttribute(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + do result[j++] = value; while (--count > 0); + } + return j; + } + /** the input device */ private java.io.Reader zzReader; @@ -131,6 +266,10 @@ class Yylex { /* user code: */ private StringBuffer sb=new StringBuffer(); +int getPosition(){ + return yychar; +} + /** @@ -163,7 +302,7 @@ private StringBuffer sb=new StringBuffer(); char [] map = new char[0x10000]; int i = 0; /* index in packed string */ int j = 0; /* index in unpacked array */ - while (i < 88) { + while (i < 90) { int count = packed.charAt(i++); char value = packed.charAt(i++); do map[j++] = value; while (--count > 0); @@ -359,7 +498,7 @@ private StringBuffer sb=new StringBuffer(); * @return the next token * @exception java.io.IOException if any I/O-Error occurs */ - public Yytoken yylex() throws java.io.IOException { + public Yytoken yylex() throws java.io.IOException, ParseException { int zzInput; int zzAction; @@ -370,10 +509,15 @@ private StringBuffer sb=new StringBuffer(); char [] zzBufferL = zzBuffer; char [] zzCMapL = ZZ_CMAP; + int [] zzTransL = ZZ_TRANS; + int [] zzRowMapL = ZZ_ROWMAP; + int [] zzAttrL = ZZ_ATTRIBUTE; while (true) { zzMarkedPosL = zzMarkedPos; + yychar+= zzMarkedPosL-zzStartRead; + zzAction = -1; zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL; @@ -408,225 +552,15 @@ private StringBuffer sb=new StringBuffer(); zzInput = zzBufferL[zzCurrentPosL++]; } } - zzInput = zzCMapL[zzInput]; + int zzNext = zzTransL[ zzRowMapL[zzState] + zzCMapL[zzInput] ]; + if (zzNext == -1) break zzForAction; + zzState = zzNext; - boolean zzIsFinal = false; - boolean zzNoLookAhead = false; - - zzForNext: { switch (zzState) { - case 0: - switch (zzInput) { - case 2: zzState = 2; break zzForNext; - case 3: zzIsFinal = true; zzState = 3; break zzForNext; - case 7: zzIsFinal = true; zzState = 4; break zzForNext; - case 9: zzIsFinal = true; zzNoLookAhead = true; zzState = 5; break zzForNext; - case 12: zzState = 6; break zzForNext; - case 13: zzState = 7; break zzForNext; - case 15: zzState = 8; break zzForNext; - case 21: zzIsFinal = true; zzNoLookAhead = true; zzState = 9; break zzForNext; - case 22: zzIsFinal = true; zzNoLookAhead = true; zzState = 10; break zzForNext; - case 23: zzIsFinal = true; zzNoLookAhead = true; zzState = 11; break zzForNext; - case 24: zzIsFinal = true; zzNoLookAhead = true; zzState = 12; break zzForNext; - case 25: zzIsFinal = true; zzNoLookAhead = true; zzState = 13; break zzForNext; - case 26: zzIsFinal = true; zzNoLookAhead = true; zzState = 14; break zzForNext; - default: break zzForAction; - } - - case 1: - switch (zzInput) { - case 8: zzState = 16; break zzForNext; - case 9: zzIsFinal = true; zzNoLookAhead = true; zzState = 17; break zzForNext; - default: zzIsFinal = true; zzState = 15; break zzForNext; - } - - case 2: - switch (zzInput) { - case 3: zzIsFinal = true; zzState = 3; break zzForNext; - default: break zzForAction; - } - - case 3: - switch (zzInput) { - case 3: zzIsFinal = true; break zzForNext; - case 4: zzState = 18; break zzForNext; - case 5: - case 17: zzState = 19; break zzForNext; - default: break zzForAction; - } - - case 4: - switch (zzInput) { - case 7: zzIsFinal = true; break zzForNext; - default: break zzForAction; - } - - case 6: - switch (zzInput) { - case 18: zzState = 20; break zzForNext; - default: break zzForAction; - } - - case 7: - switch (zzInput) { - case 16: zzState = 21; break zzForNext; - default: break zzForAction; - } - - case 8: - switch (zzInput) { - case 14: zzState = 22; break zzForNext; - default: break zzForAction; - } - - case 15: - switch (zzInput) { - case 8: - case 9: break zzForAction; - default: zzIsFinal = true; break zzForNext; - } - - case 16: - switch (zzInput) { - case 8: zzIsFinal = true; zzNoLookAhead = true; zzState = 23; break zzForNext; - case 9: zzIsFinal = true; zzNoLookAhead = true; zzState = 24; break zzForNext; - case 10: zzIsFinal = true; zzNoLookAhead = true; zzState = 25; break zzForNext; - case 11: zzIsFinal = true; zzNoLookAhead = true; zzState = 26; break zzForNext; - case 12: zzIsFinal = true; zzNoLookAhead = true; zzState = 27; break zzForNext; - case 13: zzIsFinal = true; zzNoLookAhead = true; zzState = 28; break zzForNext; - case 14: zzIsFinal = true; zzNoLookAhead = true; zzState = 29; break zzForNext; - case 15: zzIsFinal = true; zzNoLookAhead = true; zzState = 30; break zzForNext; - case 16: zzState = 31; break zzForNext; - default: break zzForAction; - } - - case 18: - switch (zzInput) { - case 3: zzIsFinal = true; zzState = 32; break zzForNext; - default: break zzForAction; - } - - case 19: - switch (zzInput) { - case 2: - case 6: zzState = 33; break zzForNext; - case 3: zzIsFinal = true; zzState = 34; break zzForNext; - default: break zzForAction; - } - - case 20: - switch (zzInput) { - case 19: zzState = 35; break zzForNext; - default: break zzForAction; - } - - case 21: - switch (zzInput) { - case 19: zzState = 36; break zzForNext; - default: break zzForAction; - } - - case 22: - switch (zzInput) { - case 16: zzState = 37; break zzForNext; - default: break zzForAction; - } - - case 31: - switch (zzInput) { - case 1: - case 3: - case 5: - case 11: - case 12: - case 17: - case 18: zzState = 38; break zzForNext; - default: break zzForAction; - } - - case 32: - switch (zzInput) { - case 5: - case 17: zzState = 19; break zzForNext; - case 3: zzIsFinal = true; break zzForNext; - default: break zzForAction; - } - - case 33: - switch (zzInput) { - case 3: zzIsFinal = true; zzState = 34; break zzForNext; - default: break zzForAction; - } - - case 34: - switch (zzInput) { - case 3: zzIsFinal = true; break zzForNext; - default: break zzForAction; - } - - case 35: - switch (zzInput) { - case 20: zzState = 37; break zzForNext; - default: break zzForAction; - } - - case 36: - switch (zzInput) { - case 19: zzIsFinal = true; zzNoLookAhead = true; zzState = 39; break zzForNext; - default: break zzForAction; - } - - case 37: - switch (zzInput) { - case 17: zzIsFinal = true; zzNoLookAhead = true; zzState = 40; break zzForNext; - default: break zzForAction; - } - - case 38: - switch (zzInput) { - case 1: - case 3: - case 5: - case 11: - case 12: - case 17: - case 18: zzState = 41; break zzForNext; - default: break zzForAction; - } - - case 41: - switch (zzInput) { - case 1: - case 3: - case 5: - case 11: - case 12: - case 17: - case 18: zzState = 42; break zzForNext; - default: break zzForAction; - } - - case 42: - switch (zzInput) { - case 1: - case 3: - case 5: - case 11: - case 12: - case 17: - case 18: zzIsFinal = true; zzNoLookAhead = true; zzState = 43; break zzForNext; - default: break zzForAction; - } - - default: - // if this is ever reached, there is a serious bug in JFlex - zzScanError(ZZ_UNKNOWN_ERROR); - break; - } } - - if ( zzIsFinal ) { + int zzAttributes = zzAttrL[zzState]; + if ( (zzAttributes & 1) == 1 ) { zzAction = zzState; zzMarkedPosL = zzCurrentPosL; - if ( zzNoLookAhead ) break zzForAction; + if ( (zzAttributes & 8) == 8 ) break zzForAction; } } @@ -636,99 +570,108 @@ private StringBuffer sb=new StringBuffer(); zzMarkedPos = zzMarkedPosL; switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) { - case 10: + case 11: { sb.append(yytext()); } - case 24: break; - case 3: + case 25: break; + case 4: { sb.delete(0, sb.length());yybegin(STRING_BEGIN); } - case 25: break; - case 15: + case 26: break; + case 16: { sb.append('\b'); } - case 26: break; - case 5: + case 27: break; + case 6: { return new Yytoken(Yytoken.TYPE_RIGHT_BRACE,null); } - case 27: break; + case 28: break; + case 23: + { Boolean val=Boolean.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE, val); + } + case 29: break; + case 22: + { return new Yytoken(Yytoken.TYPE_VALUE, null); + } + case 30: break; + case 13: + { yybegin(YYINITIAL);return new Yytoken(Yytoken.TYPE_VALUE, sb.toString()); + } + case 31: break; case 12: { sb.append('\\'); } - case 28: break; - case 20: - { Double val=Double.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE,val); - } - case 29: break; - case 1: - { Long val=Long.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE,val); - } - case 30: break; - case 7: - { return new Yytoken(Yytoken.TYPE_RIGHT_SQUARE,null); - } - case 31: break; - case 18: - { sb.append('\r'); - } case 32: break; case 21: - { return new Yytoken(Yytoken.TYPE_VALUE,null); + { Double val=Double.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE, val); } case 33: break; - case 14: - { sb.append('/'); + case 1: + { throw new ParseException(yychar, ParseException.ERROR_UNEXPECTED_CHAR, new Character(yycharat(0))); } case 34: break; - case 9: - { return new Yytoken(Yytoken.TYPE_COLON,null); + case 8: + { return new Yytoken(Yytoken.TYPE_RIGHT_SQUARE,null); } case 35: break; - case 11: - { yybegin(YYINITIAL);return new Yytoken(Yytoken.TYPE_VALUE,sb.toString()); + case 19: + { sb.append('\r'); } case 36: break; - case 13: - { sb.append('"'); + case 15: + { sb.append('/'); } case 37: break; - case 4: - { return new Yytoken(Yytoken.TYPE_LEFT_BRACE,null); + case 10: + { return new Yytoken(Yytoken.TYPE_COLON,null); } case 38: break; - case 16: - { sb.append('\f'); + case 14: + { sb.append('"'); } case 39: break; - case 19: - { sb.append('\t'); + case 5: + { return new Yytoken(Yytoken.TYPE_LEFT_BRACE,null); } case 40: break; - case 6: - { return new Yytoken(Yytoken.TYPE_LEFT_SQUARE,null); + case 17: + { sb.append('\f'); } case 41: break; - case 23: - { int ch=Integer.parseInt(yytext().substring(2),16); - sb.append((char)ch); + case 24: + { try{ + int ch=Integer.parseInt(yytext().substring(2),16); + sb.append((char)ch); + } + catch(Exception e){ + throw new ParseException(yychar, ParseException.ERROR_UNEXPECTED_EXCEPTION, e); + } } case 42: break; - case 22: - { Boolean val=Boolean.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE,val); + case 20: + { sb.append('\t'); } case 43: break; - case 17: - { sb.append('\n'); + case 7: + { return new Yytoken(Yytoken.TYPE_LEFT_SQUARE,null); } case 44: break; - case 8: - { return new Yytoken(Yytoken.TYPE_COMMA,null); + case 2: + { Long val=Long.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE, val); } case 45: break; - case 2: - { + case 18: + { sb.append('\n'); } case 46: break; + case 9: + { return new Yytoken(Yytoken.TYPE_COMMA,null); + } + case 47: break; + case 3: + { + } + case 48: break; default: if (zzInput == YYEOF && zzStartRead == zzCurrentPos) { zzAtEOF = true; diff --git a/src/org/json/simple/parser/Yytoken.java b/src/org/json/simple/parser/Yytoken.java index 46bfe64..ff14e27 100644 --- a/src/org/json/simple/parser/Yytoken.java +++ b/src/org/json/simple/parser/Yytoken.java @@ -26,6 +26,33 @@ public class Yytoken { } public String toString(){ - return String.valueOf(type+"=>|"+value+"|"); + StringBuffer sb = new StringBuffer(); + switch(type){ + case TYPE_VALUE: + sb.append("VALUE(").append(value).append(")"); + break; + case TYPE_LEFT_BRACE: + sb.append("LEFT BRACE({)"); + break; + case TYPE_RIGHT_BRACE: + sb.append("RIGHT BRACE(})"); + break; + case TYPE_LEFT_SQUARE: + sb.append("LEFT SQUARE([)"); + break; + case TYPE_RIGHT_SQUARE: + sb.append("RIGHT SQUARE(])"); + break; + case TYPE_COMMA: + sb.append("COMMA(,)"); + break; + case TYPE_COLON: + sb.append("COLON(:)"); + break; + case TYPE_EOF: + sb.append("END OF FILE"); + break; + } + return sb.toString(); } } diff --git a/test/org/json/simple/Test.java b/test/org/json/simple/Test.java index 0b44f68..fb71044 100644 --- a/test/org/json/simple/Test.java +++ b/test/org/json/simple/Test.java @@ -4,10 +4,22 @@ */ package org.json.simple; +import java.io.IOException; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; +import java.util.Map; import junit.framework.TestCase; +import org.json.simple.parser.ContainerFactory; +import org.json.simple.parser.ContentHandler; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; /** * @author FangYidong @@ -46,6 +58,241 @@ public class Test extends TestCase{ obj=JSONValue.parse(s); assertEquals("hello\bworld\"abc\tdef\\ghi\rjkl\n123δΈ­",((List)obj).get(0).toString()); + JSONParser parser = new JSONParser(); + s="{\"name\":"; + try{ + obj = parser.parse(s); + } + catch(ParseException pe){ + assertEquals(ParseException.ERROR_UNEXPECTED_TOKEN, pe.getErrorType()); + assertEquals(8, pe.getPosition()); + } + + s="{\"name\":}"; + try{ + obj = parser.parse(s); + } + catch(ParseException pe){ + assertEquals(ParseException.ERROR_UNEXPECTED_TOKEN, pe.getErrorType()); + assertEquals(8, pe.getPosition()); + } + + + s="{\"name"; + try{ + obj = parser.parse(s); + } + catch(ParseException pe){ + assertEquals(ParseException.ERROR_UNEXPECTED_TOKEN, pe.getErrorType()); + assertEquals(6, pe.getPosition()); + } + + + s = "[[null, 123.45, \"a\\\tb c\"}, true]"; + try{ + parser.parse(s); + } + catch(ParseException pe){ + assertEquals(24, pe.getPosition()); + System.out.println("Error at character position: " + pe.getPosition()); + switch(pe.getErrorType()){ + case ParseException.ERROR_UNEXPECTED_TOKEN: + System.out.println("Unexpected token: " + pe.getUnexpectedObject()); + break; + case ParseException.ERROR_UNEXPECTED_CHAR: + System.out.println("Unexpected character: " + pe.getUnexpectedObject()); + break; + case ParseException.ERROR_UNEXPECTED_EXCEPTION: + ((Exception)pe.getUnexpectedObject()).printStackTrace(); + break; + } + } + + s = "{\"first\": 123, \"second\": [4, 5, 6], \"third\": 789}"; + ContainerFactory containerFactory = new ContainerFactory(){ + public List creatArrayContainer() { + return new LinkedList(); + } + + public Map createObjectContainer() { + return new LinkedHashMap(); + } + + }; + + try{ + Map json = (Map)parser.parse(s, containerFactory); + Iterator iter = json.entrySet().iterator(); + System.out.println("==iterate result=="); + while(iter.hasNext()){ + Map.Entry entry = (Map.Entry)iter.next(); + System.out.println(entry.getKey() + "=>" + entry.getValue()); + } + + System.out.println("==toJSONString()=="); + System.out.println(JSONValue.toJSONString(json)); + assertEquals("{\"first\":123,\"second\":[4,5,6],\"third\":789}", JSONValue.toJSONString(json)); + } + catch(ParseException pe){ + pe.printStackTrace(); + } + + s = "{\"first\": 123, \"second\": [{\"s1\":{\"s11\":\"v11\"}}, 4, 5, 6], \"third\": 789}"; + ContentHandler myHandler = new ContentHandler() { + + public boolean endArray() throws ParseException { + System.out.println("endArray()"); + return true; + } + + public void endJSON() throws ParseException { + System.out.println("endJSON()"); + } + + public boolean endObject() throws ParseException { + System.out.println("endObject()"); + return true; + } + + public boolean endObjectEntry() throws ParseException { + System.out.println("endObjectEntry()"); + return true; + } + + public boolean primitive(Object value) throws ParseException { + System.out.println("primitive(): " + value); + return true; + } + + public boolean startArray() throws ParseException { + System.out.println("startArray()"); + return true; + } + + public void startJSON() throws ParseException { + System.out.println("startJSON()"); + } + + public boolean startObject() throws ParseException { + System.out.println("startObject()"); + return true; + } + + public boolean startObjectEntry(String key) throws ParseException { + System.out.println("startObjectEntry(), key:" + key); + return true; + } + + }; + try{ + parser.parse(s, myHandler); + } + catch(ParseException pe){ + pe.printStackTrace(); + } + + class KeyFinder implements ContentHandler{ + private Object value; + private boolean found = false; + private boolean end = false; + private String key; + private String matchKey; + + public void setMatchKey(String matchKey){ + this.matchKey = matchKey; + } + + public Object getValue(){ + return value; + } + + public boolean isEnd(){ + return end; + } + + public void setFound(boolean found){ + this.found = found; + } + + public boolean isFound(){ + return found; + } + + public void startJSON() throws ParseException, IOException { + found = false; + end = false; + } + + public void endJSON() throws ParseException, IOException { + end = true; + } + + public boolean primitive(Object value) throws ParseException, IOException { + if(key != null){ + if(key.equals(matchKey)){ + found = true; + this.value = value; + key = null; + return false; + } + } + return true; + } + + public boolean startArray() throws ParseException, IOException { + return true; + } + + + public boolean startObject() throws ParseException, IOException { + return true; + } + + public boolean startObjectEntry(String key) throws ParseException, IOException { + this.key = key; + return true; + } + + public boolean endArray() throws ParseException, IOException { + return false; + } + + public boolean endObject() throws ParseException, IOException { + return true; + } + + public boolean endObjectEntry() throws ParseException, IOException { + return true; + } + }; + + s = "{\"first\": 123, \"second\": [{\"k1\":{\"id\":\"id1\"}}, 4, 5, 6, {\"id\": 123}], \"third\": 789, \"id\": null}"; + parser.reset(); + KeyFinder keyFinder = new KeyFinder(); + keyFinder.setMatchKey("id"); + int i = 0; + try{ + while(!keyFinder.isEnd()){ + parser.parse(s, keyFinder, true); + if(keyFinder.isFound()){ + i++; + keyFinder.setFound(false); + System.out.println("found id:"); + System.out.println(keyFinder.getValue()); + if(i == 1) + assertEquals("id1", keyFinder.getValue()); + if(i == 2){ + assertTrue(keyFinder.getValue() instanceof Number); + assertEquals("123", String.valueOf(keyFinder.getValue())); + } + if(i == 3) + assertTrue(null == keyFinder.getValue()); + } + } + } + catch(ParseException pe){ + pe.printStackTrace(); + } } public void testEncode() throws Exception{ @@ -78,5 +325,67 @@ public class Test extends TestCase{ System.out.println(array1); System.out.println(); assertEquals("[\"abc\\u0010a\\/\",123,222.123,true,{\"weight\":60.21,\"age\":27,\"name\":\"fang\",\"is_developer\":true}]",array1.toString()); - } + + List list = new ArrayList(); + list.add("abc\u0010a/"); + list.add(new Integer(123)); + list.add(new Double(222.123)); + list.add(new Boolean(true)); + list.add(null); + System.out.println("======list=========="); + System.out.println(JSONArray.toJSONString(list)); + System.out.println(); + assertEquals("[\"abc\\u0010a\\/\",123,222.123,true,null]",JSONArray.toJSONString(list)); + + Map map = new HashMap(); + map.put("name","fang"); + map.put("age",new Integer(27)); + map.put("is_developer",new Boolean(true)); + map.put("weight",new Double(60.21)); + map.put("array1",list); + System.out.println("======map with list==========="); + System.out.println(map); + System.out.println(); + assertEquals("{\"array1\":[\"abc\\u0010a\\/\",123,222.123,true,null],\"weight\":60.21,\"age\":27,\"name\":\"fang\",\"is_developer\":true}",JSONObject.toJSONString(map)); + + Map m1 = new LinkedHashMap(); + Map m2 = new HashMap(); + List l1 = new LinkedList(); + + m1.put("k11","v11"); + m1.put("k12","v12"); + m1.put("k13", "v13"); + m2.put("k21","v21"); + m2.put("k22","v22"); + m2.put("k23","v23"); + l1.add(m1); + l1.add(m2); + String jsonString = JSONValue.toJSONString(l1); + System.out.println(jsonString); + assertEquals("[{\"k11\":\"v11\",\"k12\":\"v12\",\"k13\":\"v13\"},{\"k22\":\"v22\",\"k21\":\"v21\",\"k23\":\"v23\"}]", jsonString); + + StringWriter out = new StringWriter(); + JSONValue.writeJSONString(l1, out); + jsonString = out.toString(); + System.out.println(jsonString); + assertEquals("[{\"k11\":\"v11\",\"k12\":\"v12\",\"k13\":\"v13\"},{\"k22\":\"v22\",\"k21\":\"v21\",\"k23\":\"v23\"}]", jsonString); + + List l2 = new LinkedList(); + Map m3 = new LinkedHashMap(); + m3.put("k31", "v3"); + m3.put("k32", new Double(123.45)); + m3.put("k33", new Boolean(false)); + m3.put("k34", null); + l2.add("vvv"); + l2.add("1.23456789123456789"); + l2.add(new Boolean(true)); + l2.add(null); + m3.put("k35", l2); + m1.put("k14", m3); + out = new StringWriter(); + JSONValue.writeJSONString(l1, out); + jsonString = out.toString(); + System.out.println(jsonString); + assertEquals("[{\"k11\":\"v11\",\"k12\":\"v12\",\"k13\":\"v13\",\"k14\":{\"k31\":\"v3\",\"k32\":123.45,\"k33\":false,\"k34\":null,\"k35\":[\"vvv\",\"1.23456789123456789\",true,null]}},{\"k22\":\"v22\",\"k21\":\"v21\",\"k23\":\"v23\"}]",jsonString); + } } diff --git a/test/org/json/simple/parser/YylexTest.java b/test/org/json/simple/parser/YylexTest.java index d0eaf40..ce31751 100644 --- a/test/org/json/simple/parser/YylexTest.java +++ b/test/org/json/simple/parser/YylexTest.java @@ -1,5 +1,6 @@ package org.json.simple.parser; +import java.io.IOException; import java.io.StringReader; import junit.framework.TestCase; @@ -38,13 +39,40 @@ public class YylexTest extends TestCase { System.out.println(s); in = new StringReader(s); lexer=new Yylex(in); - Error err=null; + ParseException err=null; try{ token=lexer.yylex(); } - catch(Error e){ + catch(ParseException e){ err=e; System.out.println("error:"+err); + assertEquals(ParseException.ERROR_UNEXPECTED_CHAR, e.getErrorType()); + assertEquals(0,e.getPosition()); + assertEquals(new Character('\b'),e.getUnexpectedObject()); + } + catch(IOException ie){ + throw ie; + } + assertTrue(err!=null); + + s="{a : b}"; + System.out.println(s); + in = new StringReader(s); + lexer=new Yylex(in); + err=null; + try{ + lexer.yylex(); + token=lexer.yylex(); + } + catch(ParseException e){ + err=e; + System.out.println("error:"+err); + assertEquals(ParseException.ERROR_UNEXPECTED_CHAR, e.getErrorType()); + assertEquals(new Character('a'),e.getUnexpectedObject()); + assertEquals(1,e.getPosition()); + } + catch(IOException ie){ + throw ie; } assertTrue(err!=null); }