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