001package org.hl7.fhir.r5.elementmodel;
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.io.IOException;
035import java.io.InputStream;
036import java.io.OutputStream;
037import java.io.OutputStreamWriter;
038import java.math.BigDecimal;
039import java.util.ArrayList;
040import java.util.HashSet;
041import java.util.IdentityHashMap;
042import java.util.List;
043import java.util.Map;
044import java.util.Map.Entry;
045import java.util.Set;
046
047import org.hl7.fhir.exceptions.FHIRException;
048import org.hl7.fhir.exceptions.FHIRFormatError;
049import org.hl7.fhir.r5.conformance.ProfileUtilities;
050import org.hl7.fhir.r5.context.IWorkerContext;
051import org.hl7.fhir.r5.elementmodel.Element.SpecialElement;
052import org.hl7.fhir.r5.formats.IParser.OutputStyle;
053import org.hl7.fhir.r5.formats.JsonCreator;
054import org.hl7.fhir.r5.formats.JsonCreatorCanonical;
055import org.hl7.fhir.r5.formats.JsonCreatorGson;
056import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
057import org.hl7.fhir.r5.utils.FHIRPathEngine;
058import org.hl7.fhir.r5.model.StructureDefinition;
059import org.hl7.fhir.utilities.TextFile;
060import org.hl7.fhir.utilities.Utilities;
061import org.hl7.fhir.utilities.i18n.I18nConstants;
062import org.hl7.fhir.utilities.json.JsonTrackingParser;
063import org.hl7.fhir.utilities.json.JsonTrackingParser.LocationData;
064import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
065import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
066import org.hl7.fhir.utilities.xhtml.XhtmlParser;
067
068import com.google.gson.JsonArray;
069import com.google.gson.JsonElement;
070import com.google.gson.JsonNull;
071import com.google.gson.JsonObject;
072import com.google.gson.JsonPrimitive;
073
074public class JsonParser extends ParserBase {
075
076  private JsonCreator json;
077  private Map<JsonElement, LocationData> map;
078  private boolean allowComments;
079
080  private ProfileUtilities profileUtilities;
081
082  public JsonParser(IWorkerContext context, ProfileUtilities utilities) {
083    super(context);
084
085    this.profileUtilities = utilities;
086  }
087
088  public JsonParser(IWorkerContext context) {
089    super(context);
090
091    this.profileUtilities = new ProfileUtilities(this.context, null, null, new FHIRPathEngine(context));
092  }
093
094  public Element parse(String source, String type) throws Exception {
095    JsonObject obj = (JsonObject) new com.google.gson.JsonParser().parse(source);
096    String path = "/"+type;
097    StructureDefinition sd = getDefinition(-1, -1, type);
098    if (sd == null)
099      return null;
100
101    Element result = new Element(type, new Property(context, sd.getSnapshot().getElement().get(0), sd, this.profileUtilities));
102    result.setPath(type);
103    checkObject(obj, path);
104    result.setType(type);
105    parseChildren(path, obj, result, true);
106    result.numberChildren();
107    return result;
108  }
109
110
111  @Override
112  public List<NamedElement> parse(InputStream stream) throws IOException, FHIRException {
113    // if we're parsing at this point, then we're going to use the custom parser
114    List<NamedElement> res = new ArrayList<>();
115    map = new IdentityHashMap<JsonElement, LocationData>();
116    String source = TextFile.streamToString(stream);
117    if (policy == ValidationPolicy.EVERYTHING) {
118      JsonObject obj = null;
119      try {
120        obj = JsonTrackingParser.parse(source, map, false, allowComments);
121      } catch (Exception e) {
122        logError(-1, -1,context.formatMessage(I18nConstants.DOCUMENT), IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_, e.getMessage()), IssueSeverity.FATAL);
123        return null;
124      }
125      assert (map.containsKey(obj));
126      Element e = parse(obj);
127      if (e != null) {
128        res.add(new NamedElement(null, e));
129      }
130    } else {
131      JsonObject obj = JsonTrackingParser.parse(source, null); // (JsonObject) new com.google.gson.JsonParser().parse(source);
132      //                        assert (map.containsKey(obj));
133      Element e = parse(obj);
134      if (e != null) {
135        res.add(new NamedElement(null, e));
136      }
137    }
138    return res;
139  }
140
141  public Element parse(JsonObject object, Map<JsonElement, LocationData> map) throws FHIRException {
142    this.map = map;
143    return parse(object);
144  }
145
146  public Element parse(JsonObject object) throws FHIRException {
147    JsonElement rt = object.get("resourceType");
148    if (rt == null) {
149      logError(line(object), col(object), "$", IssueType.INVALID, context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCETYPE_PROPERTY), IssueSeverity.FATAL);
150      return null;
151    } else {
152      String name = rt.getAsString();
153      String path = name;
154
155      StructureDefinition sd = getDefinition(line(object), col(object), name);
156      if (sd == null)
157        return null;
158
159      Element result = new Element(name, new Property(context, sd.getSnapshot().getElement().get(0), sd, this.profileUtilities));
160      checkObject(object, path);
161      result.markLocation(line(object), col(object));
162      result.setType(name);
163      result.setPath(result.fhirType());
164      parseChildren(path, object, result, true);
165      result.numberChildren();
166      return result;
167    }
168  }
169
170  private void checkObject(JsonObject object, String path) throws FHIRFormatError {
171    if (policy == ValidationPolicy.EVERYTHING) {
172      boolean found = false;
173      for (Entry<String, JsonElement> e : object.entrySet()) {
174        //              if (!e.getKey().equals("fhir_comments")) {
175        found = true;
176        break;
177        //              }
178      }
179      if (!found)
180        logError(line(object), col(object), path, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_MUST_HAVE_SOME_CONTENT), IssueSeverity.ERROR);
181    }
182  }
183
184  private void parseChildren(String path, JsonObject object, Element element, boolean hasResourceType) throws FHIRException {
185    reapComments(object, element);
186    List<Property> properties = element.getProperty().getChildProperties(element.getName(), null);
187    Set<String> processed = new HashSet<String>();
188    if (hasResourceType)
189      processed.add("resourceType");
190
191    // note that we do not trouble ourselves to maintain the wire format order here - we don't even know what it was anyway
192    // first pass: process the properties
193    for (Property property : properties) {
194      parseChildItem(path, object, element, processed, property);
195    }
196
197    // second pass: check for things not processed
198    if (policy != ValidationPolicy.NONE) {
199      for (Entry<String, JsonElement> e : object.entrySet()) {
200        if (!processed.contains(e.getKey())) {
201          logError(line(e.getValue()), col(e.getValue()), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_, e.getKey()), IssueSeverity.ERROR);
202        }
203      }
204    }
205  }
206
207  public void parseChildItem(String path, JsonObject object, Element context, Set<String> processed, Property property) {
208    if (property.isChoice() || property.getDefinition().getPath().endsWith("data[x]")) {
209      for (TypeRefComponent type : property.getDefinition().getType()) {
210        String eName = property.getName().substring(0, property.getName().length()-3) + Utilities.capitalize(type.getWorkingCode());
211        if (!isPrimitive(type.getWorkingCode()) && object.has(eName)) {
212          parseChildComplex(path, object, context, processed, property, eName);
213          break;
214        } else if (isPrimitive(type.getWorkingCode()) && (object.has(eName) || object.has("_"+eName))) {
215          parseChildPrimitive(object, context, processed, property, path, eName);
216          break;
217        }
218      }
219    } else if (property.isPrimitive(property.getType(null))) {
220      parseChildPrimitive(object, context, processed, property, path, property.getName());
221    } else if (object.has(property.getName())) {
222      parseChildComplex(path, object, context, processed, property, property.getName());
223    }
224  }
225
226  private void parseChildComplex(String path, JsonObject object, Element element, Set<String> processed, Property property, String name) throws FHIRException {
227    processed.add(name);
228    String npath = path+"."+property.getName();
229    String fpath = element.getPath()+"."+property.getName();
230    JsonElement e = object.get(name);
231    if (property.isList() && (e instanceof JsonArray)) {
232      JsonArray arr = (JsonArray) e;
233      if (arr.size() == 0) {
234        logError(line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ARRAY_CANNOT_BE_EMPTY), IssueSeverity.ERROR);                     
235      }
236      int c = 0;
237      for (JsonElement am : arr) {
238        parseChildComplexInstance(npath+"["+c+"]", fpath+"["+c+"]", object, element, property, name, am);
239        c++;
240      }
241    } else {
242      if (property.isList()) {
243        logError(line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describeType(e), name, path), IssueSeverity.ERROR);
244      }
245      parseChildComplexInstance(npath, fpath, object, element, property, name, e);
246    }
247  }
248
249  private String describeType(JsonElement e) {
250    if (e.isJsonArray())
251      return "an Array";
252    if (e.isJsonObject())
253      return "an Object";
254    if (e.isJsonPrimitive())
255      return "a primitive property";
256    if (e.isJsonNull())
257      return "a Null";
258    return null;
259  }
260
261  private void parseChildComplexInstance(String npath, String fpath, JsonObject object, Element element, Property property, String name, JsonElement e) throws FHIRException {
262    if (e instanceof JsonObject) {
263      JsonObject child = (JsonObject) e;
264      Element n = new Element(name, property).markLocation(line(child), col(child));
265      n.setPath(fpath);
266      checkObject(child, npath);
267      element.getChildren().add(n);
268      if (property.isResource())
269        parseResource(npath, child, n, property);
270      else
271        parseChildren(npath, child, n, false);
272    } else
273      logError(line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE__NOT_, (property.isList() ? "an Array" : "an Object"), describe(e), name, npath), IssueSeverity.ERROR);
274  }
275
276  private String describe(JsonElement e) {
277    if (e instanceof JsonArray) {
278      return "an array";
279    }
280    if (e instanceof JsonObject) {
281      return "an object";
282    }
283    if (e instanceof JsonNull) {
284      return "null";
285    }
286    return "a primitive property";
287  }
288
289  private void parseChildPrimitive(JsonObject object, Element element, Set<String> processed, Property property, String path, String name) throws FHIRException {
290    String npath = path+"."+property.getName();
291    String fpath = element.getPath()+"."+property.getName();
292    processed.add(name);
293    processed.add("_"+name);
294    JsonElement main = object.has(name) ? object.get(name) : null;
295    JsonElement fork = object.has("_"+name) ? object.get("_"+name) : null;
296    if (main != null || fork != null) {
297      if (property.isList()) {
298        boolean ok = true;
299        if (!(main == null || main instanceof JsonArray)) {
300          logError(line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describe(main), name, path), IssueSeverity.ERROR);
301          ok = false;
302        }
303        if (!(fork == null || fork instanceof JsonArray)) {
304          logError(line(fork), col(fork), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_BASE_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describe(main), name, path), IssueSeverity.ERROR);
305          ok = false;
306        }
307        if (ok) {
308          JsonArray arr1 = (JsonArray) main;
309          JsonArray arr2 = (JsonArray) fork;
310          for (int i = 0; i < Math.max(arrC(arr1), arrC(arr2)); i++) {
311            JsonElement m = arrI(arr1, i);
312            JsonElement f = arrI(arr2, i);
313            parseChildPrimitiveInstance(element, property, name, npath, fpath, m, f);
314          }
315        }
316      } else {
317        parseChildPrimitiveInstance(element, property, name, npath, fpath, main, fork);
318      }
319    }
320  }
321
322  private JsonElement arrI(JsonArray arr, int i) {
323    return arr == null || i >= arr.size() || arr.get(i) instanceof JsonNull ? null : arr.get(i);
324  }
325
326  private int arrC(JsonArray arr) {
327    return arr == null ? 0 : arr.size();
328  }
329
330  private void parseChildPrimitiveInstance(Element element, Property property, String name, String npath, String fpath, JsonElement main, JsonElement fork) throws FHIRException {
331    if (main != null && !(main instanceof JsonPrimitive))
332      logError(line(main), col(main), npath, IssueType.INVALID, context.formatMessage(
333          I18nConstants.THIS_PROPERTY_MUST_BE_AN_SIMPLE_VALUE_NOT_, describe(main), name, npath), IssueSeverity.ERROR);
334    else if (fork != null && !(fork instanceof JsonObject))
335      logError(line(fork), col(fork), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_OBJECT_NOT_, describe(fork), name, npath), IssueSeverity.ERROR);
336    else {
337      Element n = new Element(name, property).markLocation(line(main != null ? main : fork), col(main != null ? main : fork));
338      n.setPath(fpath);
339      element.getChildren().add(n);
340      if (main != null) {
341        JsonPrimitive p = (JsonPrimitive) main;
342        if (p.isNumber() && p.getAsNumber() instanceof JsonTrackingParser.PresentedBigDecimal) {
343          String rawValue = ((JsonTrackingParser.PresentedBigDecimal) p.getAsNumber()).getPresentation();
344          n.setValue(rawValue);
345        } else {
346          n.setValue(p.getAsString());
347        }
348        if (!n.getProperty().isChoice() && n.getType().equals("xhtml")) {
349          try {
350            n.setXhtml(new XhtmlParser().setValidatorMode(policy == ValidationPolicy.EVERYTHING).parse(n.getValue(), null).getDocumentElement());
351          } catch (Exception e) {
352            logError(line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_XHTML_, e.getMessage()), IssueSeverity.ERROR);
353          }
354        }
355        if (policy == ValidationPolicy.EVERYTHING) {
356          // now we cross-check the primitive format against the stated type
357          if (Utilities.existsInList(n.getType(), "boolean")) {
358            if (!p.isBoolean())
359              logError(line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_THE_PRIMITIVE_VALUE_MUST_BE_A_BOOLEAN), IssueSeverity.ERROR);
360          } else if (Utilities.existsInList(n.getType(), "integer", "unsignedInt", "positiveInt", "decimal")) {
361            if (!p.isNumber())
362              logError(line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_THE_PRIMITIVE_VALUE_MUST_BE_A_NUMBER), IssueSeverity.ERROR);
363          } else if (!p.isString())
364            logError(line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_THE_PRIMITIVE_VALUE_MUST_BE_A_STRING), IssueSeverity.ERROR);
365        }
366      }
367      if (fork != null) {
368        JsonObject child = (JsonObject) fork;
369        checkObject(child, npath);
370        parseChildren(npath, child, n, false);
371      }
372    }
373  }
374
375
376  private void parseResource(String npath, JsonObject res, Element parent, Property elementProperty) throws FHIRException {
377    JsonElement rt = res.get("resourceType");
378    if (rt == null) {
379      logError(line(res), col(res), npath, IssueType.INVALID, context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCETYPE_PROPERTY), IssueSeverity.FATAL);
380    } else {
381      String name = rt.getAsString();
382      StructureDefinition sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(name, context.getOverrideVersionNs()));
383      if (sd == null) {
384        logError(line(res), col(res), npath, IssueType.INVALID, context.formatMessage(I18nConstants.CONTAINED_RESOURCE_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAME_, name), IssueSeverity.FATAL);                            
385      } else {
386        parent.updateProperty(new Property(context, sd.getSnapshot().getElement().get(0), sd, this.profileUtilities), SpecialElement.fromProperty(parent.getProperty()), elementProperty);
387        parent.setType(name);
388        parseChildren(npath, res, parent, true);
389      }
390    }
391  }
392
393  private void reapComments(JsonObject object, Element context) {
394    if (object.has("fhir_comments")) {
395      JsonArray arr = object.getAsJsonArray("fhir_comments");
396      for (JsonElement e : arr) {
397        context.getComments().add(e.getAsString());
398      }
399    }
400  }
401
402  private int line(JsonElement e) {
403    if (map == null|| !map.containsKey(e))
404      return -1;
405    else
406      return map.get(e).getLine();
407  }
408
409  private int col(JsonElement e) {
410    if (map == null|| !map.containsKey(e))
411      return -1;
412    else
413      return map.get(e).getCol();
414  }
415
416
417  protected void prop(String name, String value, String link) throws IOException {
418    json.link(link);
419    if (name != null)
420      json.name(name);
421    json.value(value);
422  }
423
424  protected void open(String name, String link) throws IOException {
425    json.link(link);
426    if (name != null)
427      json.name(name);
428    json.beginObject();
429  }
430
431  protected void close() throws IOException {
432    json.endObject();
433  }
434
435  protected void openArray(String name, String link) throws IOException {
436    json.link(link);
437    if (name != null)
438      json.name(name);
439    json.beginArray();
440  }
441
442  protected void closeArray() throws IOException {
443    json.endArray();
444  }
445
446
447  @Override
448  public void compose(Element e, OutputStream stream, OutputStyle style, String identity) throws FHIRException, IOException {
449    OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8");
450    if (style == OutputStyle.CANONICAL)
451      json = new JsonCreatorCanonical(osw);
452    else
453      json = new JsonCreatorGson(osw);
454    json.setIndent(style == OutputStyle.PRETTY ? "  " : "");
455    json.beginObject();
456    prop("resourceType", e.getType(), null);
457    Set<String> done = new HashSet<String>();
458    for (Element child : e.getChildren()) {
459      compose(e.getName(), e, done, child);
460    }
461    json.endObject();
462    json.finish();
463    osw.flush();
464  }
465
466  public void compose(Element e, JsonCreator json) throws Exception {
467    this.json = json;
468    json.beginObject();
469
470    prop("resourceType", e.getType(), linkResolver == null ? null : linkResolver.resolveProperty(e.getProperty()));
471    Set<String> done = new HashSet<String>();
472    for (Element child : e.getChildren()) {
473      compose(e.getName(), e, done, child);
474    }
475    json.endObject();
476    json.finish();
477  }
478
479  private void compose(String path, Element e, Set<String> done, Element child) throws IOException {
480    boolean isList = child.hasElementProperty() ? child.getElementProperty().isList() : child.getProperty().isList();
481    if (!isList) {// for specials, ignore the cardinality of the stated type
482      compose(path, child);
483    } else if (!done.contains(child.getName())) {
484      done.add(child.getName());
485      List<Element> list = e.getChildrenByName(child.getName());
486      composeList(path, list);
487    }
488  }
489
490  private void composeList(String path, List<Element> list) throws IOException {
491    // there will be at least one element
492    String name = list.get(0).getName();
493    boolean complex = true;
494    if (list.get(0).isPrimitive()) {
495      boolean prim = false;
496      complex = false;
497      for (Element item : list) {
498        if (item.hasValue())
499          prim = true;
500        if (item.hasChildren())
501          complex = true;
502      }
503      if (prim) {
504        openArray(name, linkResolver == null ? null : linkResolver.resolveProperty(list.get(0).getProperty()));
505        for (Element item : list) {
506          if (item.hasValue())
507            primitiveValue(null, item);
508          else
509            json.nullValue();
510        }
511        closeArray();
512      }
513      name = "_"+name;
514    }
515    if (complex) {
516      openArray(name, linkResolver == null ? null : linkResolver.resolveProperty(list.get(0).getProperty()));
517      for (Element item : list) {
518        if (item.hasChildren()) {
519          open(null,null);
520          if (item.getProperty().isResource()) {
521            prop("resourceType", item.getType(), linkResolver == null ? null : linkResolver.resolveType(item.getType()));
522          }
523          Set<String> done = new HashSet<String>();
524          for (Element child : item.getChildren()) {
525            compose(path+"."+name+"[]", item, done, child);
526          }
527          close();
528        } else
529          json.nullValue();
530      }
531      closeArray();
532    }
533  }
534
535  private void primitiveValue(String name, Element item) throws IOException {
536    if (name != null) {
537      if (linkResolver != null)
538        json.link(linkResolver.resolveProperty(item.getProperty()));
539      json.name(name);
540    }
541    String type = item.getType();
542    if (Utilities.existsInList(type, "boolean"))
543      json.value(item.getValue().trim().equals("true") ? new Boolean(true) : new Boolean(false));
544    else if (Utilities.existsInList(type, "integer", "unsignedInt", "positiveInt"))
545      json.value(new Integer(item.getValue()));
546    else if (Utilities.existsInList(type, "decimal"))
547      try {
548        json.value(new BigDecimal(item.getValue()));
549      } catch (Exception e) {
550        throw new NumberFormatException(context.formatMessage(I18nConstants.ERROR_WRITING_NUMBER__TO_JSON, item.getValue()));
551      }
552    else
553      json.value(item.getValue());
554  }
555
556  private void compose(String path, Element element) throws IOException {
557    String name = element.getName();
558    if (element.isPrimitive() || isPrimitive(element.getType())) {
559      if (element.hasValue())
560        primitiveValue(name, element);
561      name = "_"+name;
562      if (element.getType().equals("xhtml"))
563        json.anchor("end-xhtml");
564    }
565    if (element.hasChildren()) {
566      open(name, linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty()));
567      if (element.getProperty().isResource()) {
568        prop("resourceType", element.getType(), linkResolver == null ? null : linkResolver.resolveType(element.getType()));
569      }
570      Set<String> done = new HashSet<String>();
571      for (Element child : element.getChildren()) {
572        compose(path+"."+element.getName(), element, done, child);
573      }
574      close();
575    }
576  }
577
578  public boolean isAllowComments() {
579    return allowComments;
580  }
581
582  public JsonParser setAllowComments(boolean allowComments) {
583    this.allowComments = allowComments;
584    return this;
585  }
586
587
588}