001package org.hl7.fhir.r4.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 case End: 368 // TODO GG: This isn't handled. Should it be? 369 break; 370 } 371 next(); 372 } 373 } 374 375 private void readArray(JsonArray arr, boolean root) throws FHIRException { 376 while (!((itemType == ItemType.End) || (root && (itemType == ItemType.Eof)))) { 377 switch (itemType) { 378 case Object: 379 JsonObject obj = new JsonObject(); // (arr.path+'['+inttostr(i)+']'); 380 LocationData loc = lexer.location.copy(); 381 arr.add(obj); 382 next(); 383 readObject(obj, false); 384 map.put(obj, loc); 385 break; 386 case String: 387 JsonPrimitive v = new JsonPrimitive(itemValue); 388 arr.add(v); 389 map.put(v, lexer.location.copy()); 390 break; 391 case Number: 392 v = new JsonPrimitive(new BigDecimal(itemValue)); 393 arr.add(v); 394 map.put(v, lexer.location.copy()); 395 break; 396 case Null : 397 JsonNull n = new JsonNull(); 398 arr.add(n); 399 map.put(n, lexer.location.copy()); 400 break; 401 case Array: 402 JsonArray child = new JsonArray(); // (arr.path+'['+inttostr(i)+']'); 403 loc = lexer.location.copy(); 404 arr.add(child); 405 next(); 406 readArray(child, false); 407 map.put(arr, loc); 408 break; 409 case Eof : 410 throw lexer.error("Unexpected End of File"); 411 case End: 412 case Boolean: 413 // TODO GG: These aren't handled. SHould they be? 414 break; 415 } 416 next(); 417 } 418 } 419 420 private void next() throws FHIRException { 421 switch (itemType) { 422 case Object : 423 lexer.consume(TokenType.Open); 424 lexer.states.push(new State(itemName, false)); 425 if (lexer.getType() == TokenType.Close) { 426 itemType = ItemType.End; 427 lexer.next(); 428 } else 429 parseProperty(); 430 break; 431 case Null: 432 case String: 433 case Number: 434 case End: 435 case Boolean : 436 if (itemType == ItemType.End) 437 lexer.states.pop(); 438 if (lexer.getType() == TokenType.Comma) { 439 lexer.next(); 440 parseProperty(); 441 } else if (lexer.getType() == TokenType.Close) { 442 itemType = ItemType.End; 443 lexer.next(); 444 } else if (lexer.getType() == TokenType.CloseArray) { 445 itemType = ItemType.End; 446 lexer.next(); 447 } else if (lexer.getType() == TokenType.Eof) { 448 itemType = ItemType.Eof; 449 } else 450 throw lexer.error("Unexpected JSON syntax"); 451 break; 452 case Array : 453 lexer.next(); 454 lexer.states.push(new State(itemName+"[]", true)); 455 parseProperty(); 456 break; 457 case Eof : 458 throw lexer.error("JSON Syntax Error - attempt to read past end of json stream"); 459 default: 460 throw lexer.error("not done yet (a): "+itemType.toString()); 461 } 462 } 463 464 private void parseProperty() throws FHIRException { 465 if (!lexer.states.peek().isProp) { 466 itemName = lexer.consume(TokenType.String); 467 itemValue = null; 468 lexer.consume(TokenType.Colon); 469 } 470 switch (lexer.getType()) { 471 case Null : 472 itemType = ItemType.Null; 473 itemValue = lexer.value; 474 lexer.next(); 475 break; 476 case String : 477 itemType = ItemType.String; 478 itemValue = lexer.value; 479 lexer.next(); 480 break; 481 case Boolean : 482 itemType = ItemType.Boolean; 483 itemValue = lexer.value; 484 lexer.next(); 485 break; 486 case Number : 487 itemType = ItemType.Number; 488 itemValue = lexer.value; 489 lexer.next(); 490 break; 491 case Open : 492 itemType = ItemType.Object; 493 break; 494 case OpenArray : 495 itemType = ItemType.Array; 496 break; 497 case CloseArray : 498 itemType = ItemType.End; 499 break; 500 // case Close, , case Colon, case Comma, case OpenArray, ! 501 default: 502 throw lexer.error("not done yet (b): "+lexer.getType().toString()); 503 } 504 } 505}