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