001package org.hl7.fhir.r4.formats;
002/*
003Copyright (c) 2011+, HL7, Inc
004All rights reserved.
005
006Redistribution and use in source and binary forms, with or without modification, 
007are permitted provided that the following conditions are met:
008
009 * Redistributions of source code must retain the above copyright notice, this 
010   list of conditions and the following disclaimer.
011 * Redistributions in binary form must reproduce the above copyright notice, 
012   this list of conditions and the following disclaimer in the documentation 
013   and/or other materials provided with the distribution.
014 * Neither the name of HL7 nor the names of its contributors may be used to 
015   endorse or promote products derived from this software without specific 
016   prior written permission.
017
018THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
019ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
020WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
021IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
022INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
023NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
024PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
025WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
026ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
027POSSIBILITY OF SUCH DAMAGE.
028
029*/
030
031import java.io.IOException;
032import java.io.InputStream;
033import java.io.OutputStream;
034import java.io.OutputStreamWriter;
035import java.math.BigDecimal;
036import java.util.List;
037
038import org.hl7.fhir.r4.model.DomainResource;
039import org.hl7.fhir.r4.model.Element;
040import org.hl7.fhir.r4.model.IdType;
041import org.hl7.fhir.r4.model.Resource;
042import org.hl7.fhir.r4.model.StringType;
043import org.hl7.fhir.r4.model.Type;
044import org.hl7.fhir.r4.utils.formats.JsonTrackingParser;
045import org.hl7.fhir.exceptions.FHIRFormatError;
046import org.hl7.fhir.instance.model.api.IIdType;
047import org.hl7.fhir.utilities.TextFile;
048import org.hl7.fhir.utilities.Utilities;
049import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
050import org.hl7.fhir.utilities.xhtml.XhtmlNode;
051import org.hl7.fhir.utilities.xhtml.XhtmlParser;
052
053import com.google.gson.JsonArray;
054import com.google.gson.JsonObject;
055import com.google.gson.JsonSyntaxException;
056/**
057 * General parser for JSON content. You instantiate an JsonParser of these, but you 
058 * actually use parse or parseGeneral defined on this class
059 * 
060 * The two classes are separated to keep generated and manually maintained code apart.
061 */
062public abstract class JsonParserBase extends ParserBase implements IParser {
063        
064  @Override
065  public ParserType getType() {
066          return ParserType.JSON;
067  }
068
069        // private static com.google.gson.JsonParser  parser = new com.google.gson.JsonParser();
070  
071  // -- in descendent generated code --------------------------------------
072  
073  abstract protected Resource parseResource(JsonObject json) throws IOException, FHIRFormatError;
074  abstract protected Type parseType(JsonObject json, String type) throws IOException, FHIRFormatError;
075  abstract protected Type parseAnyType(JsonObject json, String type) throws IOException, FHIRFormatError;
076  abstract protected Type parseType(String prefix, JsonObject json) throws IOException, FHIRFormatError;
077  abstract protected boolean hasTypeName(JsonObject json, String prefix);
078  abstract protected void composeResource(Resource resource) throws IOException;
079  abstract protected void composeTypeInner(Type type) throws IOException;
080
081  /* -- entry points --------------------------------------------------- */
082
083  /**
084   * @throws FHIRFormatError 
085   * Parse content that is known to be a resource
086   * @throws IOException 
087   * @throws  
088   */
089  @Override
090  public Resource parse(InputStream input) throws IOException, FHIRFormatError {
091    JsonObject json = loadJson(input);
092    return parseResource(json);
093  }
094
095  /**
096   * parse xml that is known to be a resource, and that has already been read into a JSON object  
097   * @throws IOException 
098   * @throws FHIRFormatError 
099   */
100  public Resource parse(JsonObject json) throws FHIRFormatError, IOException {
101    return parseResource(json);
102  }
103
104  @Override
105  public Type parseType(InputStream input, String type) throws IOException, FHIRFormatError {
106    JsonObject json = loadJson(input);
107    return parseType(json, type);
108  }
109
110  @Override
111  public Type parseAnyType(InputStream input, String type) throws IOException, FHIRFormatError {
112    JsonObject json = loadJson(input);
113    return parseAnyType(json, type);
114  }
115
116  /**
117   * Compose a resource to a stream, possibly using pretty presentation for a human reader (used in the spec, for example, but not normally in production)
118   * @throws IOException 
119   */
120  @Override
121  public void compose(OutputStream stream, Resource resource) throws IOException {
122    OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8");
123    if (style == OutputStyle.CANONICAL)
124      json = new JsonCreatorCanonical(osw);
125    else
126      json = new JsonCreatorDirect(osw); // use this instead of Gson because this preserves decimal formatting
127    json.setIndent(style == OutputStyle.PRETTY ? "  " : "");
128    json.beginObject();
129    composeResource(resource);
130    json.endObject();
131    json.finish();
132    osw.flush();
133  }
134
135  /**
136   * Compose a resource using a pre-existing JsonWriter
137   * @throws IOException 
138   */
139  public void compose(JsonCreator writer, Resource resource) throws IOException {
140    json = writer;
141    composeResource(resource);
142  }
143  
144  @Override
145  public void compose(OutputStream stream, Type type, String rootName) throws IOException {
146    OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8");
147    if (style == OutputStyle.CANONICAL)
148      json = new JsonCreatorCanonical(osw);
149    else
150      json = new JsonCreatorDirect(osw);// use this instead of Gson because this preserves decimal formatting
151    json.setIndent(style == OutputStyle.PRETTY ? "  " : "");
152    json.beginObject();
153    composeTypeInner(type);
154    json.endObject();
155    json.finish();
156    osw.flush();
157  }
158    
159
160  
161  /* -- json routines --------------------------------------------------- */
162
163  protected JsonCreator json;
164  private boolean htmlPretty;
165  
166  private JsonObject loadJson(InputStream input) throws JsonSyntaxException, IOException {
167    return JsonTrackingParser.parse(TextFile.streamToString(input), null);
168    // return parser.parse(TextFile.streamToString(input)).getAsJsonObject();
169  }
170  
171//  private JsonObject loadJson(String input) {
172//    return parser.parse(input).getAsJsonObject();
173//  }
174//  
175  protected void parseElementProperties(JsonObject json, Element e) throws IOException, FHIRFormatError {
176    if (json != null && json.has("id"))
177      e.setId(json.get("id").getAsString());
178    if (!Utilities.noString(e.getId()))
179      idMap.put(e.getId(), e);
180    if (json.has("fhir_comments") && handleComments) {
181      JsonArray array = json.getAsJsonArray("fhir_comments");
182      for (int i = 0; i < array.size(); i++) {
183        e.getFormatCommentsPre().add(array.get(i).getAsString());
184      }
185    }
186  }
187  
188  protected XhtmlNode parseXhtml(String value) throws IOException, FHIRFormatError {
189    XhtmlParser prsr = new XhtmlParser();
190    try {
191                return prsr.parse(value, "div").getChildNodes().get(0);
192        } catch (org.hl7.fhir.exceptions.FHIRFormatError e) {
193                throw new FHIRFormatError(e.getMessage(), e);
194        }
195  }
196  
197  protected DomainResource parseDomainResource(JsonObject json) throws FHIRFormatError, IOException {
198          return (DomainResource) parseResource(json);
199  }
200
201        protected void writeNull(String name) throws IOException {
202                json.nullValue();
203        }
204        protected void prop(String name, String value) throws IOException {
205                if (name != null)
206                        json.name(name);
207                json.value(value);
208        }
209
210  protected void prop(String name, java.lang.Boolean value) throws IOException {
211    if (name != null)
212      json.name(name);
213    json.value(value);
214  }
215
216  protected void prop(String name, BigDecimal value) throws IOException {
217    if (name != null)
218      json.name(name);
219    json.value(value);
220  }
221
222  protected void propNum(String name, String value) throws IOException {
223    if (name != null)
224      json.name(name);
225    json.valueNum(value);
226  }
227
228  protected void prop(String name, java.lang.Integer value) throws IOException {
229    if (name != null)
230      json.name(name);
231    json.value(value);
232  }
233
234        protected void composeXhtml(String name, XhtmlNode html) throws IOException {
235                if (!Utilities.noString(xhtmlMessage)) {
236      prop(name, "<div>!-- "+xhtmlMessage+" --></div>");
237                } else {
238                XhtmlComposer comp = new XhtmlComposer(XhtmlComposer.XML, htmlPretty);
239                prop(name, comp.compose(html));
240                }
241        }
242
243        protected void open(String name) throws IOException {
244                if (name != null) 
245                        json.name(name);
246                json.beginObject();
247        }
248
249        protected void close() throws IOException {
250                json.endObject();
251        }
252
253        protected void openArray(String name) throws IOException {
254                if (name != null) 
255                        json.name(name);
256                json.beginArray();
257        }
258
259        protected void closeArray() throws IOException {
260                json.endArray();
261        }
262
263        protected void openObject(String name) throws IOException {
264                if (name != null) 
265                        json.name(name);
266                json.beginObject();
267        }
268
269        protected void closeObject() throws IOException {
270                json.endObject();
271        }
272
273//  protected void composeBinary(String name, Binary element) {
274//    if (element != null) {
275//      prop("resourceType", "Binary");
276//      if (element.getXmlId() != null)
277//        prop("id", element.getXmlId());
278//      prop("contentType", element.getContentType());
279//      prop("content", toString(element.getContent()));
280//    }    
281//    
282//  }
283
284  protected boolean anyHasExtras(List<? extends Element> list) {
285          for (Element e : list) {
286                if (e.hasExtension() || !Utilities.noString(e.getId()))
287                        return true;
288          }
289          return false;
290  }
291
292        protected boolean makeComments(Element element) {
293                return handleComments && (style != OutputStyle.CANONICAL) && !(element.getFormatCommentsPre().isEmpty() && element.getFormatCommentsPost().isEmpty());
294        }
295        
296  protected void composeDomainResource(String name, DomainResource e) throws IOException {
297          openObject(name);
298          composeResource(e);
299          close();
300          
301  }
302
303  protected abstract void composeType(String prefix, Type type) throws IOException;
304
305  
306  abstract void composeStringCore(String name, StringType value, boolean inArray) throws IOException;
307
308  protected void composeStringCore(String name, IIdType value, boolean inArray) throws IOException {
309          composeStringCore(name, new StringType(value.getValue()), inArray);
310  }    
311
312  abstract void composeStringExtras(String name, StringType value, boolean inArray) throws IOException;
313
314  protected void composeStringExtras(String name, IIdType value, boolean inArray) throws IOException {
315          composeStringExtras(name, new StringType(value.getValue()), inArray);
316  }    
317  
318  protected void parseElementProperties(JsonObject theAsJsonObject, IIdType theReferenceElement) throws FHIRFormatError, IOException {
319          parseElementProperties(theAsJsonObject, (Element)theReferenceElement);
320  }
321
322  protected void parseElementProperties(JsonObject theAsJsonObject, IdType theReferenceElement) throws FHIRFormatError, IOException {
323          parseElementProperties(theAsJsonObject, (Element)theReferenceElement);
324  }
325
326}