001package org.hl7.fhir.dstu3.utils.formats; 002 003import java.math.BigDecimal; 004import java.util.Map; 005import java.util.Stack; 006 007import org.hl7.fhir.exceptions.FHIRException; 008import org.hl7.fhir.utilities.Utilities; 009 010import com.google.gson.JsonArray; 011import com.google.gson.JsonElement; 012import com.google.gson.JsonNull; 013import com.google.gson.JsonObject; 014import com.google.gson.JsonPrimitive; 015 016 017/** 018 * This is created to get a json parser that can track line numbers... grr... 019 * 020 * @author Grahame Grieve 021 * 022 */ 023public class JsonTrackingParser { 024 025 public enum TokenType { 026 Open, Close, String, Number, Colon, Comma, OpenArray, CloseArray, Eof, Null, Boolean; 027 } 028 029 public class LocationData { 030 private int line; 031 private int col; 032 033 protected LocationData(int line, int col) { 034 super(); 035 this.line = line; 036 this.col = col; 037 } 038 039 public int getLine() { 040 return line; 041 } 042 043 public int getCol() { 044 return col; 045 } 046 047 public void newLine() { 048 line++; 049 col = 1; 050 } 051 052 public LocationData copy() { 053 return new LocationData(line, col); 054 } 055 } 056 057 private class State { 058 private String name; 059 private boolean isProp; 060 protected State(String name, boolean isProp) { 061 super(); 062 this.name = name; 063 this.isProp = isProp; 064 } 065 public String getName() { 066 return name; 067 } 068 public boolean isProp() { 069 return isProp; 070 } 071 } 072 073 private class Lexer { 074 private String source; 075 private int cursor; 076 private String peek; 077 private String value; 078 private TokenType type; 079 private Stack<State> states = new Stack<State>(); 080 private LocationData lastLocationBWS; 081 private LocationData lastLocationAWS; 082 private LocationData location; 083 private StringBuilder b = new StringBuilder(); 084 085 public Lexer(String source) throws FHIRException { 086 this.source = source; 087 cursor = -1; 088 location = new LocationData(1, 1); 089 start(); 090 } 091 092 private boolean more() { 093 return peek != null || cursor < source.length(); 094 } 095 096 private String getNext(int length) throws FHIRException { 097 String result = ""; 098 if (peek != null) { 099 if (peek.length() > length) { 100 result = peek.substring(0, length); 101 peek = peek.substring(length); 102 } else { 103 result = peek; 104 peek = null; 105 } 106 } 107 if (result.length() < length) { 108 int len = length - result.length(); 109 if (cursor > source.length() - len) 110 throw error("Attempt to read past end of source"); 111 result = result + source.substring(cursor+1, cursor+len+1); 112 cursor = cursor + len; 113 } 114 for (char ch : result.toCharArray()) 115 if (ch == '\n') 116 location.newLine(); 117 else 118 location.col++; 119 return result; 120 } 121 122 private char getNextChar() throws FHIRException { 123 if (peek != null) { 124 char ch = peek.charAt(0); 125 peek = peek.length() == 1 ? null : peek.substring(1); 126 return ch; 127 } else { 128 cursor++; 129 if (cursor >= source.length()) 130 return (char) 0; 131 char ch = source.charAt(cursor); 132 if (ch == '\n') { 133 location.newLine(); 134 } else { 135 location.col++; 136 } 137 return ch; 138 } 139 } 140 141 private void push(char ch){ 142 peek = peek == null ? String.valueOf(ch) : String.valueOf(ch)+peek; 143 } 144 145 private void parseWord(String word, char ch, TokenType type) throws FHIRException { 146 this.type = type; 147 value = ""+ch+getNext(word.length()-1); 148 if (!value.equals(word)) 149 throw error("Syntax error in json reading special word "+word); 150 } 151 152 private FHIRException error(String msg) { 153 return new FHIRException("Error parsing JSON source: "+msg+" at Line "+Integer.toString(location.line)+" (path=["+path()+"])"); 154 } 155 156 private String path() { 157 if (states.empty()) 158 return value; 159 else { 160 String result = ""; 161 for (State s : states) 162 result = result + '/'+ s.getName(); 163 result = result + value; 164 return result; 165 } 166 } 167 168 public void start() throws FHIRException { 169// char ch = getNextChar(); 170// if (ch = '\.uEF') 171// begin 172// // skip BOM 173// getNextChar(); 174// getNextChar(); 175// end 176// else 177// push(ch); 178 next(); 179 } 180 181 public TokenType getType() { 182 return type; 183 } 184 185 public String getValue() { 186 return value; 187 } 188 189 190 public LocationData getLastLocationBWS() { 191 return lastLocationBWS; 192 } 193 194 public LocationData getLastLocationAWS() { 195 return lastLocationAWS; 196 } 197 198 public void next() throws FHIRException { 199 lastLocationBWS = location.copy(); 200 char ch; 201 do { 202 ch = getNextChar(); 203 } while (more() && Utilities.charInSet(ch, ' ', '\r', '\n', '\t')); 204 lastLocationAWS = location.copy(); 205 206 if (!more()) { 207 type = TokenType.Eof; 208 } else { 209 switch (ch) { 210 case '{' : 211 type = TokenType.Open; 212 break; 213 case '}' : 214 type = TokenType.Close; 215 break; 216 case '"' : 217 type = TokenType.String; 218 b.setLength(0); 219 do { 220 ch = getNextChar(); 221 if (ch == '\\') { 222 ch = getNextChar(); 223 switch (ch) { 224 case '"': b.append('"'); break; 225 case '\\': b.append('\\'); break; 226 case '/': b.append('/'); break; 227 case 'n': b.append('\n'); break; 228 case 'r': b.append('\r'); break; 229 case 't': b.append('\t'); break; 230 case 'u': b.append((char) Integer.parseInt(getNext(4), 16)); break; 231 default : 232 throw error("unknown escape sequence: \\"+ch); 233 } 234 ch = ' '; 235 } else if (ch != '"') 236 b.append(ch); 237 } while (more() && (ch != '"')); 238 if (!more()) 239 throw error("premature termination of json stream during a string"); 240 value = b.toString(); 241 break; 242 case ':' : 243 type = TokenType.Colon; 244 break; 245 case ',' : 246 type = TokenType.Comma; 247 break; 248 case '[' : 249 type = TokenType.OpenArray; 250 break; 251 case ']' : 252 type = TokenType.CloseArray; 253 break; 254 case 't' : 255 parseWord("true", ch, TokenType.Boolean); 256 break; 257 case 'f' : 258 parseWord("false", ch, TokenType.Boolean); 259 break; 260 case 'n' : 261 parseWord("null", ch, TokenType.Null); 262 break; 263 default: 264 if ((ch >= '0' && ch <= '9') || ch == '-') { 265 type = TokenType.Number; 266 b.setLength(0); 267 while (more() && ((ch >= '0' && ch <= '9') || ch == '-' || ch == '.')) { 268 b.append(ch); 269 ch = getNextChar(); 270 } 271 value = b.toString(); 272 push(ch); 273 } else 274 throw error("Unexpected char '"+ch+"' in json stream"); 275 } 276 } 277 } 278 279 public String consume(TokenType type) throws FHIRException { 280 if (this.type != type) 281 throw error("JSON syntax error - found "+type.toString()+" expecting "+type.toString()); 282 String result = value; 283 next(); 284 return result; 285 } 286 287 } 288 289 enum ItemType { 290 Object, String, Number, Boolean, Array, End, Eof, Null; 291 } 292 private Map<JsonElement, LocationData> map; 293 private Lexer lexer; 294 private ItemType itemType = ItemType.Object; 295 private String itemName; 296 private String itemValue; 297 298 public static JsonObject parse(String source, Map<JsonElement, LocationData> map) throws FHIRException { 299 JsonTrackingParser self = new JsonTrackingParser(); 300 self.map = map; 301 return self.parse(source); 302 } 303 304 private JsonObject parse(String source) throws FHIRException { 305 lexer = new Lexer(source); 306 JsonObject result = new JsonObject(); 307 LocationData loc = lexer.location.copy(); 308 if (lexer.getType() == TokenType.Open) { 309 lexer.next(); 310 lexer.states.push(new State("", false)); 311 } 312 else 313 throw lexer.error("Unexpected content at start of JSON: "+lexer.getType().toString()); 314 315 parseProperty(); 316 readObject(result, true); 317 map.put(result, loc); 318 return result; 319 } 320 321 private void readObject(JsonObject obj, boolean root) throws FHIRException { 322 map.put(obj, lexer.location.copy()); 323 324 while (!(itemType == ItemType.End) || (root && (itemType == ItemType.Eof))) { 325 if (obj.has(itemName)) 326 throw lexer.error("Duplicated property name: "+itemName); 327 328 switch (itemType) { 329 case Object: 330 JsonObject child = new JsonObject(); //(obj.path+'.'+ItemName); 331 LocationData loc = lexer.location.copy(); 332 obj.add(itemName, child); 333 next(); 334 readObject(child, false); 335 map.put(obj, loc); 336 break; 337 case Boolean : 338 JsonPrimitive v = new JsonPrimitive(Boolean.valueOf(itemValue)); 339 obj.add(itemName, v); 340 map.put(v, lexer.location.copy()); 341 break; 342 case String: 343 v = new JsonPrimitive(itemValue); 344 obj.add(itemName, v); 345 map.put(v, lexer.location.copy()); 346 break; 347 case Number: 348 v = new JsonPrimitive(new BigDecimal(itemValue)); 349 obj.add(itemName, v); 350 map.put(v, lexer.location.copy()); 351 break; 352 case Null: 353 JsonNull n = new JsonNull(); 354 obj.add(itemName, n); 355 map.put(n, lexer.location.copy()); 356 break; 357 case Array: 358 JsonArray arr = new JsonArray(); // (obj.path+'.'+ItemName); 359 loc = lexer.location.copy(); 360 obj.add(itemName, arr); 361 next(); 362 readArray(arr, false); 363 map.put(arr, loc); 364 break; 365 case Eof : 366 throw lexer.error("Unexpected End of File"); 367 } 368 next(); 369 } 370 } 371 372 private void readArray(JsonArray arr, boolean root) throws FHIRException { 373 while (!((itemType == ItemType.End) || (root && (itemType == ItemType.Eof)))) { 374 switch (itemType) { 375 case Object: 376 JsonObject obj = new JsonObject(); // (arr.path+'['+inttostr(i)+']'); 377 LocationData loc = lexer.location.copy(); 378 arr.add(obj); 379 next(); 380 readObject(obj, false); 381 map.put(obj, loc); 382 break; 383 case String: 384 JsonPrimitive v = new JsonPrimitive(itemValue); 385 arr.add(v); 386 map.put(v, lexer.location.copy()); 387 break; 388 case Number: 389 v = new JsonPrimitive(new BigDecimal(itemValue)); 390 arr.add(v); 391 map.put(v, lexer.location.copy()); 392 break; 393 case Null : 394 JsonNull n = new JsonNull(); 395 arr.add(n); 396 map.put(n, lexer.location.copy()); 397 break; 398 case Array: 399 JsonArray child = new JsonArray(); // (arr.path+'['+inttostr(i)+']'); 400 loc = lexer.location.copy(); 401 arr.add(child); 402 next(); 403 readArray(child, false); 404 map.put(arr, loc); 405 break; 406 case Eof : 407 throw lexer.error("Unexpected End of File"); 408 } 409 next(); 410 } 411 } 412 413 private void next() throws FHIRException { 414 switch (itemType) { 415 case Object : 416 lexer.consume(TokenType.Open); 417 lexer.states.push(new State(itemName, false)); 418 if (lexer.getType() == TokenType.Close) { 419 itemType = ItemType.End; 420 lexer.next(); 421 } else 422 parseProperty(); 423 break; 424 case Null: 425 case String: 426 case Number: 427 case End: 428 case Boolean : 429 if (itemType == ItemType.End) 430 lexer.states.pop(); 431 if (lexer.getType() == TokenType.Comma) { 432 lexer.next(); 433 parseProperty(); 434 } else if (lexer.getType() == TokenType.Close) { 435 itemType = ItemType.End; 436 lexer.next(); 437 } else if (lexer.getType() == TokenType.CloseArray) { 438 itemType = ItemType.End; 439 lexer.next(); 440 } else if (lexer.getType() == TokenType.Eof) { 441 itemType = ItemType.Eof; 442 } else 443 throw lexer.error("Unexpected JSON syntax"); 444 break; 445 case Array : 446 lexer.next(); 447 lexer.states.push(new State(itemName+"[]", true)); 448 parseProperty(); 449 break; 450 case Eof : 451 throw lexer.error("JSON Syntax Error - attempt to read past end of json stream"); 452 default: 453 throw lexer.error("not done yet (a): "+itemType.toString()); 454 } 455 } 456 457 private void parseProperty() throws FHIRException { 458 if (!lexer.states.peek().isProp) { 459 itemName = lexer.consume(TokenType.String); 460 itemValue = null; 461 lexer.consume(TokenType.Colon); 462 } 463 switch (lexer.getType()) { 464 case Null : 465 itemType = ItemType.Null; 466 itemValue = lexer.value; 467 lexer.next(); 468 break; 469 case String : 470 itemType = ItemType.String; 471 itemValue = lexer.value; 472 lexer.next(); 473 break; 474 case Boolean : 475 itemType = ItemType.Boolean; 476 itemValue = lexer.value; 477 lexer.next(); 478 break; 479 case Number : 480 itemType = ItemType.Number; 481 itemValue = lexer.value; 482 lexer.next(); 483 break; 484 case Open : 485 itemType = ItemType.Object; 486 break; 487 case OpenArray : 488 itemType = ItemType.Array; 489 break; 490 case CloseArray : 491 itemType = ItemType.End; 492 break; 493 // case Close, , case Colon, case Comma, case OpenArray, ! 494 default: 495 throw lexer.error("not done yet (b): "+lexer.getType().toString()); 496 } 497 } 498}