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