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