001package ca.uhn.fhir.parser.json; 002/* 003 * #%L 004 * HAPI FHIR - Core Library 005 * %% 006 * Copyright (C) 2014 - 2017 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 GsonStructure (JsonObject json) { 056 super(); 057 setNativeObject(json); 058 } 059 public GsonStructure (JsonArray json) { 060 super(); 061 setNativeArray(json); 062 } 063 064 public void setNativeObject (JsonObject json) { 065 this.rootType = ROOT_TYPE.OBJECT; 066 this.nativeRoot = json; 067 } 068 public void setNativeArray (JsonArray json) { 069 this.rootType = ROOT_TYPE.ARRAY; 070 this.nativeRoot = json; 071 } 072 073 @Override 074 public JsonLikeStructure getInstance() { 075 return new GsonStructure(); 076 } 077 078 @Override 079 public void load(Reader theReader) throws DataFormatException { 080 this.load(theReader, false); 081 } 082 083 @Override 084 public void load(Reader theReader, boolean allowArray) throws DataFormatException { 085 PushbackReader pbr = new PushbackReader(theReader); 086 int nextInt; 087 try { 088 while(true) { 089 nextInt = pbr.read(); 090 if (nextInt == -1) { 091 throw new DataFormatException("Did not find any content to parse"); 092 } 093 if (nextInt == '{') { 094 pbr.unread(nextInt); 095 break; 096 } 097 if (Character.isWhitespace(nextInt)) { 098 continue; 099 } 100 if (allowArray) { 101 if (nextInt == '[') { 102 pbr.unread(nextInt); 103 break; 104 } 105 throw new DataFormatException("Content does not appear to be FHIR JSON, first non-whitespace character was: '" + (char)nextInt + "' (must be '{' or '[')"); 106 } 107 throw new DataFormatException("Content does not appear to be FHIR JSON, first non-whitespace character was: '" + (char)nextInt + "' (must be '{')"); 108 } 109 110 Gson gson = new GsonBuilder().disableHtmlEscaping().create(); 111 if (nextInt == '{') { 112 JsonObject root = gson.fromJson(pbr, JsonObject.class); 113 setNativeObject(root); 114 } else 115 if (nextInt == '[') { 116 JsonArray root = gson.fromJson(pbr, JsonArray.class); 117 setNativeArray(root); 118 } 119 } catch (JsonSyntaxException e) { 120 if (e.getMessage().startsWith("Unexpected char 39")) { 121 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); 122 } 123 throw new DataFormatException("Failed to parse JSON encoded FHIR content: " + e.getMessage(), e); 124 } catch (Exception e) { 125 throw new DataFormatException("Failed to parse JSON content, error was: " + e.getMessage(), e); 126 } 127 } 128 129 @Override 130 public JsonLikeWriter getJsonLikeWriter (Writer writer) { 131 if (null == jsonLikeWriter) { 132 jsonLikeWriter = new GsonWriter(writer); 133 } 134 return jsonLikeWriter; 135 } 136 137 @Override 138 public JsonLikeWriter getJsonLikeWriter () { 139 if (null == jsonLikeWriter) { 140 jsonLikeWriter = new GsonWriter(); 141 } 142 return jsonLikeWriter; 143 } 144 145 @Override 146 public JsonLikeObject getRootObject() throws DataFormatException { 147 if (rootType == ROOT_TYPE.OBJECT) { 148 if (null == jsonLikeRoot) { 149 jsonLikeRoot = new GsonJsonObject((JsonObject)nativeRoot); 150 } 151 return jsonLikeRoot.getAsObject(); 152 } 153 throw new DataFormatException("Content must be a valid JSON Object. It must start with '{'."); 154 } 155 156 @Override 157 public JsonLikeArray getRootArray() throws DataFormatException { 158 if (rootType == ROOT_TYPE.ARRAY) { 159 if (null == jsonLikeRoot) { 160 jsonLikeRoot = new GsonJsonArray((JsonArray)nativeRoot); 161 } 162 return jsonLikeRoot.getAsArray(); 163 } 164 throw new DataFormatException("Content must be a valid JSON Array. It must start with '['."); 165 } 166 167 private static class GsonJsonObject extends JsonLikeObject { 168 private JsonObject nativeObject; 169 private Set<String> keySet = null; 170 private Map<String,JsonLikeValue> jsonLikeMap = new LinkedHashMap<String,JsonLikeValue>(); 171 172 public GsonJsonObject (JsonObject json) { 173 this.nativeObject = json; 174 } 175 176 @Override 177 public Object getValue() { 178 return null; 179 } 180 181 @Override 182 public Set<String> keySet() { 183 if (null == keySet) { 184 Set<Entry<String, JsonElement>> entrySet = nativeObject.entrySet(); 185 keySet = new EntryOrderedSet<String>(entrySet.size()); 186 for (Entry<String,?> entry : entrySet) { 187 keySet.add(entry.getKey()); 188 } 189 } 190 return keySet; 191 } 192 193 @Override 194 public JsonLikeValue get(String key) { 195 JsonLikeValue result = null; 196 if (jsonLikeMap.containsKey(key)) { 197 result = jsonLikeMap.get(key); 198 } else { 199 JsonElement child = nativeObject.get(key); 200 if (child != null) { 201 result = new GsonJsonValue(child); 202 } 203 jsonLikeMap.put(key, result); 204 } 205 return result; 206 } 207 } 208 209 private static class GsonJsonArray extends JsonLikeArray { 210 private JsonArray nativeArray; 211 private Map<Integer,JsonLikeValue> jsonLikeMap = new LinkedHashMap<Integer,JsonLikeValue>(); 212 213 public GsonJsonArray (JsonArray json) { 214 this.nativeArray = json; 215 } 216 217 @Override 218 public Object getValue() { 219 return null; 220 } 221 222 @Override 223 public int size() { 224 return nativeArray.size(); 225 } 226 227 @Override 228 public JsonLikeValue get(int index) { 229 Integer key = Integer.valueOf(index); 230 JsonLikeValue result = null; 231 if (jsonLikeMap.containsKey(key)) { 232 result = jsonLikeMap.get(key); 233 } else { 234 JsonElement child = nativeArray.get(index); 235 if (child != null) { 236 result = new GsonJsonValue(child); 237 } 238 jsonLikeMap.put(key, result); 239 } 240 return result; 241 } 242 } 243 244 private static class GsonJsonValue extends JsonLikeValue { 245 private JsonElement nativeValue; 246 private JsonLikeObject jsonLikeObject = null; 247 private JsonLikeArray jsonLikeArray = null; 248 249 public GsonJsonValue (JsonElement json) { 250 this.nativeValue = json; 251 } 252 253 @Override 254 public Object getValue() { 255 if (nativeValue != null && nativeValue.isJsonPrimitive()) { 256 if (((JsonPrimitive)nativeValue).isNumber()) { 257 return nativeValue.getAsNumber(); 258 } 259 if (((JsonPrimitive)nativeValue).isBoolean()) { 260 return Boolean.valueOf(nativeValue.getAsBoolean()); 261 } 262 return nativeValue.getAsString(); 263 } 264 return null; 265 } 266 267 @Override 268 public ValueType getJsonType() { 269 if (null == nativeValue || nativeValue.isJsonNull()) { 270 return ValueType.NULL; 271 } 272 if (nativeValue.isJsonObject()) { 273 return ValueType.OBJECT; 274 } 275 if (nativeValue.isJsonArray()) { 276 return ValueType.ARRAY; 277 } 278 if (nativeValue.isJsonPrimitive()) { 279 return ValueType.SCALAR; 280 } 281 return null; 282 } 283 284 @Override 285 public ScalarType getDataType() { 286 if (nativeValue != null && nativeValue.isJsonPrimitive()) { 287 if (((JsonPrimitive)nativeValue).isNumber()) { 288 return ScalarType.NUMBER; 289 } 290 if (((JsonPrimitive)nativeValue).isString()) { 291 return ScalarType.STRING; 292 } 293 if (((JsonPrimitive)nativeValue).isBoolean()) { 294 return ScalarType.BOOLEAN; 295 } 296 } 297 return null; 298 } 299 300 @Override 301 public JsonLikeArray getAsArray() { 302 if (nativeValue != null && nativeValue.isJsonArray()) { 303 if (null == jsonLikeArray) { 304 jsonLikeArray = new GsonJsonArray((JsonArray)nativeValue); 305 } 306 } 307 return jsonLikeArray; 308 } 309 310 @Override 311 public JsonLikeObject getAsObject() { 312 if (nativeValue != null && nativeValue.isJsonObject()) { 313 if (null == jsonLikeObject) { 314 jsonLikeObject = new GsonJsonObject((JsonObject)nativeValue); 315 } 316 } 317 return jsonLikeObject; 318 } 319 320 @Override 321 public Number getAsNumber() { 322 return nativeValue != null ? nativeValue.getAsNumber() : null; 323 } 324 325 @Override 326 public String getAsString() { 327 return nativeValue != null ? nativeValue.getAsString() : null; 328 } 329 330 @Override 331 public boolean getAsBoolean() { 332 if (nativeValue != null && nativeValue.isJsonPrimitive() && ((JsonPrimitive)nativeValue).isBoolean()) { 333 return nativeValue.getAsBoolean(); 334 } 335 return super.getAsBoolean(); 336 } 337 } 338 339 private static class EntryOrderedSet<T> extends AbstractSet<T> { 340 private transient ArrayList<T> data = null; 341 342 public EntryOrderedSet (int initialCapacity) { 343 data = new ArrayList<T>(initialCapacity); 344 } 345 @SuppressWarnings("unused") 346 public EntryOrderedSet () { 347 data = new ArrayList<T>(); 348 } 349 350 @Override 351 public int size() { 352 return data.size(); 353 } 354 355 @Override 356 public boolean contains(Object o) { 357 return data.contains(o); 358 } 359 360 @SuppressWarnings("unused") // not really.. just not here 361 public T get(int index) { 362 return data.get(index); 363 } 364 365 @Override 366 public boolean add(T element) { 367 if (data.contains(element)) { 368 return false; 369 } 370 return data.add(element); 371 } 372 373 @Override 374 public boolean remove(Object o) { 375 return data.remove(o); 376 } 377 378 @Override 379 public void clear() { 380 data.clear(); 381 } 382 383 @Override 384 public Iterator<T> iterator() { 385 return data.iterator(); 386 } 387 388 } 389}