View Javadoc
1   package ca.uhn.fhir.parser.json;
2   /*
3    * #%L
4    * HAPI FHIR - Core Library
5    * %%
6    * Copyright (C) 2014 - 2018 University Health Network
7    * %%
8    * Licensed under the Apache License, Version 2.0 (the "License");
9    * you may not use this file except in compliance with the License.
10   * You may obtain a copy of the License at
11   * 
12   *      http://www.apache.org/licenses/LICENSE-2.0
13   * 
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   * #L%
20   */
21  
22  import java.io.PushbackReader;
23  import java.io.Reader;
24  import java.io.Writer;
25  import java.util.AbstractSet;
26  import java.util.ArrayList;
27  import java.util.Iterator;
28  import java.util.LinkedHashMap;
29  import java.util.Map;
30  import java.util.Map.Entry;
31  import java.util.Set;
32  
33  import ca.uhn.fhir.parser.DataFormatException;
34  
35  import com.google.gson.Gson;
36  import com.google.gson.GsonBuilder;
37  import com.google.gson.JsonArray;
38  import com.google.gson.JsonElement;
39  import com.google.gson.JsonObject;
40  import com.google.gson.JsonPrimitive;
41  import com.google.gson.JsonSyntaxException;
42  
43  public class GsonStructure implements JsonLikeStructure {
44  
45  	private enum ROOT_TYPE {OBJECT, ARRAY};
46  	private ROOT_TYPE rootType = null;
47  	private JsonElement nativeRoot = null;
48  	private JsonLikeValue jsonLikeRoot = null;
49  	private GsonWriter jsonLikeWriter = null;
50  	
51  	public GsonStructure() {
52  		super();
53  	}
54  	
55  	public GsonStructure (JsonObject json) {
56  		super();
57  		setNativeObject(json);
58  	}
59  	public GsonStructure (JsonArray json) {
60  		super();
61  		setNativeArray(json);
62  	}
63  	
64  	public void setNativeObject (JsonObject json) {
65  		this.rootType = ROOT_TYPE.OBJECT;
66  		this.nativeRoot = json;
67  	}
68  	public void setNativeArray (JsonArray json) {
69  		this.rootType = ROOT_TYPE.ARRAY;
70  		this.nativeRoot = json;
71  	}
72  
73  	@Override
74  	public JsonLikeStructure getInstance() {
75  		return new GsonStructure();
76  	}
77  
78  	@Override
79  	public void load(Reader theReader) throws DataFormatException {
80  		this.load(theReader, false);		
81  	}
82  
83  	@Override
84  	public void load(Reader theReader, boolean allowArray) throws DataFormatException {
85  		PushbackReader pbr = new PushbackReader(theReader);
86  		int nextInt;
87  		try {
88  			while(true) {
89  					nextInt = pbr.read();
90  				if (nextInt == -1) {
91  					throw new DataFormatException("Did not find any content to parse");
92  				}
93  				if (nextInt == '{') {
94  					pbr.unread(nextInt);
95  					break;
96  				}
97  				if (Character.isWhitespace(nextInt)) {
98  					continue;
99  				}
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 }