001package ca.uhn.fhir.parser.json; 002/* 003 * #%L 004 * HAPI FHIR - Core Library 005 * %% 006 * Copyright (C) 2014 - 2020 University Health Network 007 * %% 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 * #L% 020 */ 021 022import java.io.PushbackReader; 023import java.io.Reader; 024import java.io.Writer; 025import java.util.AbstractSet; 026import java.util.ArrayList; 027import java.util.Iterator; 028import java.util.LinkedHashMap; 029import java.util.Map; 030import java.util.Map.Entry; 031import java.util.Set; 032 033import ca.uhn.fhir.parser.DataFormatException; 034 035import com.google.gson.Gson; 036import com.google.gson.GsonBuilder; 037import com.google.gson.JsonArray; 038import com.google.gson.JsonElement; 039import com.google.gson.JsonObject; 040import com.google.gson.JsonPrimitive; 041import com.google.gson.JsonSyntaxException; 042 043public class GsonStructure implements JsonLikeStructure { 044 045 private enum ROOT_TYPE {OBJECT, ARRAY}; 046 private ROOT_TYPE rootType = null; 047 private JsonElement nativeRoot = null; 048 private JsonLikeValue jsonLikeRoot = null; 049 private GsonWriter jsonLikeWriter = null; 050 051 public GsonStructure() { 052 super(); 053 } 054 055 public void setNativeObject (JsonObject json) { 056 this.rootType = ROOT_TYPE.OBJECT; 057 this.nativeRoot = json; 058 } 059 public void setNativeArray (JsonArray json) { 060 this.rootType = ROOT_TYPE.ARRAY; 061 this.nativeRoot = json; 062 } 063 064 @Override 065 public JsonLikeStructure getInstance() { 066 return new GsonStructure(); 067 } 068 069 @Override 070 public void load(Reader theReader) throws DataFormatException { 071 this.load(theReader, false); 072 } 073 074 @Override 075 public void load(Reader theReader, boolean allowArray) throws DataFormatException { 076 PushbackReader pbr = new PushbackReader(theReader); 077 int nextInt; 078 try { 079 while(true) { 080 nextInt = pbr.read(); 081 if (nextInt == -1) { 082 throw new DataFormatException("Did not find any content to parse"); 083 } 084 if (nextInt == '{') { 085 pbr.unread(nextInt); 086 break; 087 } 088 if (Character.isWhitespace(nextInt)) { 089 continue; 090 } 091 if (allowArray) { 092 if (nextInt == '[') { 093 pbr.unread(nextInt); 094 break; 095 } 096 throw new DataFormatException("Content does not appear to be FHIR JSON, first non-whitespace character was: '" + (char)nextInt + "' (must be '{' or '[')"); 097 } 098 throw new DataFormatException("Content does not appear to be FHIR JSON, first non-whitespace character was: '" + (char)nextInt + "' (must be '{')"); 099 } 100 101 Gson gson = new GsonBuilder().disableHtmlEscaping().create(); 102 if (nextInt == '{') { 103 JsonObject root = gson.fromJson(pbr, JsonObject.class); 104 setNativeObject(root); 105 } else if (nextInt == '[') { 106 JsonArray root = gson.fromJson(pbr, JsonArray.class); 107 setNativeArray(root); 108 } 109 } catch (JsonSyntaxException e) { 110 if (e.getMessage().startsWith("Unexpected char 39")) { 111 throw new DataFormatException("Failed to parse JSON encoded FHIR content: " + e.getMessage() + " - This may indicate that single quotes are being used as JSON escapes where double quotes are required", e); 112 } 113 throw new DataFormatException("Failed to parse JSON encoded FHIR content: " + e.getMessage(), e); 114 } catch (Exception e) { 115 throw new DataFormatException("Failed to parse JSON content, error was: " + e.getMessage(), e); 116 } 117 } 118 119 @Override 120 public JsonLikeWriter getJsonLikeWriter (Writer writer) { 121 if (null == jsonLikeWriter) { 122 jsonLikeWriter = new GsonWriter(writer); 123 } 124 return jsonLikeWriter; 125 } 126 127 @Override 128 public JsonLikeWriter getJsonLikeWriter () { 129 if (null == jsonLikeWriter) { 130 jsonLikeWriter = new GsonWriter(); 131 } 132 return jsonLikeWriter; 133 } 134 135 @Override 136 public JsonLikeObject getRootObject() throws DataFormatException { 137 if (rootType == ROOT_TYPE.OBJECT) { 138 if (null == jsonLikeRoot) { 139 jsonLikeRoot = new GsonJsonObject((JsonObject)nativeRoot); 140 } 141 return jsonLikeRoot.getAsObject(); 142 } 143 throw new DataFormatException("Content must be a valid JSON Object. It must start with '{'."); 144 } 145 146 @Override 147 public JsonLikeArray getRootArray() throws DataFormatException { 148 if (rootType == ROOT_TYPE.ARRAY) { 149 if (null == jsonLikeRoot) { 150 jsonLikeRoot = new GsonJsonArray((JsonArray)nativeRoot); 151 } 152 return jsonLikeRoot.getAsArray(); 153 } 154 throw new DataFormatException("Content must be a valid JSON Array. It must start with '['."); 155 } 156 157 private static class GsonJsonObject extends JsonLikeObject { 158 private JsonObject nativeObject; 159 private Set<String> keySet = null; 160 private Map<String,JsonLikeValue> jsonLikeMap = new LinkedHashMap<String,JsonLikeValue>(); 161 162 public GsonJsonObject (JsonObject json) { 163 this.nativeObject = json; 164 } 165 166 @Override 167 public Object getValue() { 168 return null; 169 } 170 171 @Override 172 public Set<String> keySet() { 173 if (null == keySet) { 174 Set<Entry<String, JsonElement>> entrySet = nativeObject.entrySet(); 175 keySet = new EntryOrderedSet<String>(entrySet.size()); 176 for (Entry<String,?> entry : entrySet) { 177 keySet.add(entry.getKey()); 178 } 179 } 180 return keySet; 181 } 182 183 @Override 184 public JsonLikeValue get(String key) { 185 JsonLikeValue result = null; 186 if (jsonLikeMap.containsKey(key)) { 187 result = jsonLikeMap.get(key); 188 } else { 189 JsonElement child = nativeObject.get(key); 190 if (child != null) { 191 result = new GsonJsonValue(child); 192 } 193 jsonLikeMap.put(key, result); 194 } 195 return result; 196 } 197 } 198 199 private static class GsonJsonArray extends JsonLikeArray { 200 private JsonArray nativeArray; 201 private Map<Integer,JsonLikeValue> jsonLikeMap = new LinkedHashMap<Integer,JsonLikeValue>(); 202 203 public GsonJsonArray (JsonArray json) { 204 this.nativeArray = json; 205 } 206 207 @Override 208 public Object getValue() { 209 return null; 210 } 211 212 @Override 213 public int size() { 214 return nativeArray.size(); 215 } 216 217 @Override 218 public JsonLikeValue get(int index) { 219 Integer key = Integer.valueOf(index); 220 JsonLikeValue result = null; 221 if (jsonLikeMap.containsKey(key)) { 222 result = jsonLikeMap.get(key); 223 } else { 224 JsonElement child = nativeArray.get(index); 225 if (child != null) { 226 result = new GsonJsonValue(child); 227 } 228 jsonLikeMap.put(key, result); 229 } 230 return result; 231 } 232 } 233 234 private static class GsonJsonValue extends JsonLikeValue { 235 private JsonElement nativeValue; 236 private JsonLikeObject jsonLikeObject = null; 237 private JsonLikeArray jsonLikeArray = null; 238 239 public GsonJsonValue (JsonElement json) { 240 this.nativeValue = json; 241 } 242 243 @Override 244 public Object getValue() { 245 if (nativeValue != null && nativeValue.isJsonPrimitive()) { 246 if (((JsonPrimitive)nativeValue).isNumber()) { 247 return nativeValue.getAsNumber(); 248 } 249 if (((JsonPrimitive)nativeValue).isBoolean()) { 250 return Boolean.valueOf(nativeValue.getAsBoolean()); 251 } 252 return nativeValue.getAsString(); 253 } 254 return null; 255 } 256 257 @Override 258 public ValueType getJsonType() { 259 if (null == nativeValue || nativeValue.isJsonNull()) { 260 return ValueType.NULL; 261 } 262 if (nativeValue.isJsonObject()) { 263 return ValueType.OBJECT; 264 } 265 if (nativeValue.isJsonArray()) { 266 return ValueType.ARRAY; 267 } 268 if (nativeValue.isJsonPrimitive()) { 269 return ValueType.SCALAR; 270 } 271 return null; 272 } 273 274 @Override 275 public ScalarType getDataType() { 276 if (nativeValue != null && nativeValue.isJsonPrimitive()) { 277 if (((JsonPrimitive)nativeValue).isNumber()) { 278 return ScalarType.NUMBER; 279 } 280 if (((JsonPrimitive)nativeValue).isString()) { 281 return ScalarType.STRING; 282 } 283 if (((JsonPrimitive)nativeValue).isBoolean()) { 284 return ScalarType.BOOLEAN; 285 } 286 } 287 return null; 288 } 289 290 @Override 291 public JsonLikeArray getAsArray() { 292 if (nativeValue != null && nativeValue.isJsonArray()) { 293 if (null == jsonLikeArray) { 294 jsonLikeArray = new GsonJsonArray((JsonArray)nativeValue); 295 } 296 } 297 return jsonLikeArray; 298 } 299 300 @Override 301 public JsonLikeObject getAsObject() { 302 if (nativeValue != null && nativeValue.isJsonObject()) { 303 if (null == jsonLikeObject) { 304 jsonLikeObject = new GsonJsonObject((JsonObject)nativeValue); 305 } 306 } 307 return jsonLikeObject; 308 } 309 310 @Override 311 public Number getAsNumber() { 312 return nativeValue != null ? nativeValue.getAsNumber() : null; 313 } 314 315 @Override 316 public String getAsString() { 317 return nativeValue != null ? nativeValue.getAsString() : null; 318 } 319 320 @Override 321 public boolean getAsBoolean() { 322 if (nativeValue != null && nativeValue.isJsonPrimitive() && ((JsonPrimitive)nativeValue).isBoolean()) { 323 return nativeValue.getAsBoolean(); 324 } 325 return super.getAsBoolean(); 326 } 327 } 328 329 private static class EntryOrderedSet<T> extends AbstractSet<T> { 330 private transient ArrayList<T> data = null; 331 332 public EntryOrderedSet (int initialCapacity) { 333 data = new ArrayList<T>(initialCapacity); 334 } 335 @SuppressWarnings("unused") 336 public EntryOrderedSet () { 337 data = new ArrayList<T>(); 338 } 339 340 @Override 341 public int size() { 342 return data.size(); 343 } 344 345 @Override 346 public boolean contains(Object o) { 347 return data.contains(o); 348 } 349 350 @SuppressWarnings("unused") // not really.. just not here 351 public T get(int index) { 352 return data.get(index); 353 } 354 355 @Override 356 public boolean add(T element) { 357 if (data.contains(element)) { 358 return false; 359 } 360 return data.add(element); 361 } 362 363 @Override 364 public boolean remove(Object o) { 365 return data.remove(o); 366 } 367 368 @Override 369 public void clear() { 370 data.clear(); 371 } 372 373 @Override 374 public Iterator<T> iterator() { 375 return data.iterator(); 376 } 377 378 } 379}