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}