001package org.hl7.fhir.utilities.json.model;
002
003import java.text.ParseException;
004import java.text.SimpleDateFormat;
005import java.time.Instant;
006import java.time.OffsetDateTime;
007import java.time.ZoneOffset;
008import java.util.ArrayList;
009import java.util.Date;
010import java.util.HashMap;
011import java.util.List;
012import java.util.Map;
013import java.util.Map.Entry;
014
015import org.hl7.fhir.utilities.Utilities;
016import org.hl7.fhir.utilities.json.JsonException;
017
018
019public class JsonObject extends JsonElement {
020  
021  private List<JsonProperty> properties = new ArrayList<>();
022  private Map<String, JsonProperty> propMap = new HashMap<>();
023  private boolean extraComma; // json5 support
024  
025  public JsonElementType type() {
026    return JsonElementType.OBJECT;
027  }
028
029  public JsonObject add(String name, JsonElement value) throws JsonException {
030    check(name != null, "Name is null");
031    check(value != null, "Value is null");
032    if (get(name) != null) {
033      check(false, "Name '"+name+"' already exists (value = "+get(name).toString()+")");
034    }
035    JsonProperty p = new JsonProperty(name, value);
036    properties.add(p);
037    propMap.put(name, p);
038    return this;
039  }
040
041  // this is used by the parser which can allow duplicates = true (for the validator). You should not otherwise use it
042  public JsonObject addForParser(String name, JsonElement value, boolean noComma, boolean nameUnquoted, boolean valueUnquoted) throws JsonException {
043    check(name != null, "Name is null");
044    check(value != null, "Value is null");
045    JsonProperty p = new JsonProperty(name, value);
046    p.setNoComma(noComma);
047    p.setUnquotedName(nameUnquoted);
048    p.setUnquotedValue(valueUnquoted);
049    properties.add(p);
050    propMap.put(name, p); // last duplicate wins
051    return this;
052  }
053
054  public JsonObject add(String name, String value) throws JsonException {
055    check(name != null, "Name is null");
056    return add(name, value == null ? new JsonNull() : new JsonString(value));
057  }
058
059  public JsonObject add(String name, boolean value) throws JsonException {
060    check(name != null, "Name is null");
061    return add(name, new JsonBoolean(value));
062  }
063
064  public JsonObject add(String name, int value) throws JsonException {
065    check(name != null, "Name is null");
066    return add(name, new JsonNumber(value));
067  }
068
069  public JsonObject set(String name, JsonElement value) throws JsonException {
070    check(name != null, "Name is null");
071    check(value != null, "Value is null");
072    JsonProperty p = propMap.get(name);
073    if (p != null) {
074      p.setValue(value);
075      return this;
076    } else {
077      return add(name, value);
078    }
079  }
080
081  public JsonObject set(String name, Instant value) throws JsonException {
082    String v = value == null ? null : value.toString();
083    return set(name, v);
084  }
085    
086
087  public JsonObject set(String name, String value) throws JsonException {
088    check(name != null, "Name is null");
089    JsonProperty p = propMap.get(name);
090    if (p != null) {
091      p.setValue(value == null ? new JsonNull() : new JsonString(value));
092      return this;
093    } else {
094      return add(name, value == null ? new JsonNull() : new JsonString(value));
095    }
096  }
097
098  public JsonObject set(String name, boolean value) throws JsonException {
099    check(name != null, "Name is null");
100    JsonProperty p = propMap.get(name);
101    if (p != null) {
102      p.setValue(new JsonBoolean(value));
103      return this;
104    } else {
105      return add(name, new JsonBoolean(value));
106    }
107  }
108
109  public JsonObject set(String name, int value) throws JsonException {
110    check(name != null, "Name is null");
111    JsonProperty p = propMap.get(name);
112    if (p != null) {
113      p.setValue(new JsonNumber(value));
114      return this;
115    } else {
116      return add(name, new JsonNumber(value));
117    }
118  }
119
120  public JsonElement get(String name) {
121    if (propMap.containsKey(name)) {
122      return propMap.get(name).getValue();
123    } else {
124      return null;
125    }
126  }
127
128  public boolean has(String name) {
129    return propMap.containsKey(name);
130  }
131
132  public boolean has(String... names) {
133    for (String n : names) {
134      if (propMap.containsKey(n)) {
135        return true;
136      }
137    }
138    return false;
139  }
140
141  public void remove(String name) {
142    if (propMap.containsKey(name)) {
143      propMap.remove(name);
144      properties.removeIf((JsonProperty item) -> name.equals(item.getName()));
145    }
146  }
147  
148  public List<JsonProperty> getProperties() {
149    return properties;
150  }
151
152  public List<String> getNames() {
153    return Utilities.sorted(propMap.keySet());
154  }
155
156  public String str(String name) {
157    if (hasPrimitive(name)) {
158      return get(name).asJsonPrimitive().getValue();
159    } else {
160      return null;
161    }
162  }
163
164  public boolean hasObject(String name) {
165    return propMap.containsKey(name) && propMap.get(name).getValue().type() == JsonElementType.OBJECT;
166  }
167
168  public boolean hasArray(String name) {
169    return propMap.containsKey(name) && propMap.get(name).getValue().type() == JsonElementType.ARRAY;
170  }
171
172  public boolean hasPrimitive(String name) {
173    return propMap.containsKey(name) && propMap.get(name).getValue() instanceof JsonPrimitive;
174  }
175
176  public boolean hasString(String name) {
177    return propMap.containsKey(name) && propMap.get(name).getValue().type() == JsonElementType.STRING;
178  }
179
180  public boolean hasNumber(String name) {
181    return propMap.containsKey(name) && propMap.get(name).getValue().type() == JsonElementType.NUMBER;
182  }
183
184  public boolean hasBoolean(String name) {
185    return propMap.containsKey(name) && propMap.get(name).getValue().type() == JsonElementType.BOOLEAN;
186  }
187
188  public boolean hasNull(String name) {
189    return propMap.containsKey(name) && propMap.get(name).getValue().type() == JsonElementType.NULL;
190  }
191
192
193  public JsonObject getJsonObject(String name) {
194    return hasObject(name) ?  (JsonObject) get(name) : null;
195  }
196
197  public JsonString getJsonString(String name) {
198    return hasString(name) ? (JsonString) get(name) : null;
199  }
200
201  public JsonBoolean getJsonBoolean(String name) {
202    return hasBoolean(name) ? (JsonBoolean) get(name) : null;
203  }
204  
205  public JsonNumber getJsonNumber(String name) {
206    return hasNumber(name) ? (JsonNumber) get(name) : null;
207  }
208  
209  public JsonNull getJsonNull(String name) {
210    return hasNull(name) ?(JsonNull) get(name) : null;
211  }
212  
213  public JsonArray getJsonArray(String name) {
214    return hasArray(name) ? (JsonArray) get(name) : null;
215  }
216
217  public Integer asInteger(String name) {
218    if (hasNumber(name)) {
219      return ((JsonNumber) get(name)).getInteger();
220    }
221    if (hasPrimitive(name)) {
222      String s = asString(name);
223      if (Utilities.isInteger(s)) {
224        return Integer.parseInt(s);
225      }
226    }
227    return null;
228  }
229
230  public String asString(String name) {
231    return hasPrimitive(name) ? ((JsonPrimitive) get(name)).getValue() : null;
232  }
233
234  public String asString(String... names) {
235    for (String n : names) {
236      if (hasPrimitive(n)) {
237        return asString(n);
238      }
239    }
240    return null;
241  }
242
243  public boolean asBoolean(String name) {
244    if (hasBoolean(name)) {
245      return ((JsonBoolean) get(name)).isValue();
246    }
247    if (hasPrimitive(name)) {
248      String s = asString(name);
249      if ("true".equals(s)) {
250        return true;
251      }
252      if ("false".equals(s)) {
253        return false;
254      }
255    }
256    return false;
257  }
258
259  public Instant asDate(String name) {
260    String source = asString(name);
261    if (Utilities.noString(source)) {
262      return null;
263    } else {
264      OffsetDateTime odt = OffsetDateTime.parse(source);
265      return odt.toInstant();
266    }
267  }
268  
269  public Instant asInstant(String name) throws ParseException {
270    String source = asString(name);
271    if (Utilities.noString(source) || "null".equals(source)) {
272      return null;
273    } else if (source.length() <= 10) {
274      Date d = new SimpleDateFormat("yyyy-mm-dd").parse(source);
275      return d.toInstant();
276    } else {
277      OffsetDateTime odt = OffsetDateTime.parse(source);
278      return odt.toInstant();
279    }
280  }
281  
282  public JsonObject forceObject(String name) throws JsonException {
283    if (has(name) && !hasObject(name)) {
284      remove(name);
285    }
286    if (!has(name)) {
287      add(name, new JsonObject());
288    }
289    return getJsonObject(name);
290  }
291
292  public JsonArray forceArray(String name) throws JsonException {
293    if (has(name) && !hasArray(name)) {
294      remove(name);
295    }
296    if (!has(name)) {
297      add(name, new JsonArray());
298    }
299    return getJsonArray(name);
300  }
301
302  public List<JsonObject> getJsonObjects(String name) {
303    List<JsonObject> res = new ArrayList<>();
304    if (hasArray(name)) {
305      res.addAll(getJsonArray(name).asJsonObjects());
306    } else if (hasObject(name)) {
307      res.add(getJsonObject(name));
308    } 
309    return res;
310  }
311  
312  public List<String> getStrings(String name) {
313    List<String> res = new ArrayList<>();
314    if (hasArray(name)) {
315      res.addAll(getJsonArray(name).asStrings());
316    } else if (hasPrimitive(name)) {
317      res.add(asString(name));
318    } 
319    return res;
320  }
321  
322  public JsonObject deepCopy() {
323    return (JsonObject) make().copy(this);
324  }
325
326  @Override
327  protected JsonElement copy(JsonElement other) {
328    JsonObject o = (JsonObject) other;
329    for (JsonProperty p : o.getProperties()) {
330      add(p.getName(), p.getValue().deepCopy());
331    }
332    return this;
333  }
334  
335  @Override
336  protected JsonElement make() {
337    return new JsonObject();
338  }
339  
340  public JsonObject findByStringProp(String arrName, String prop, String value) {
341    for (JsonObject obj : getJsonObjects(arrName)) {
342      if (obj.has(prop) && value.equals(obj.asString(prop))) 
343        return obj;
344    }
345    return null;
346  }
347  
348  public void merge(JsonObject source) {
349    for (JsonProperty pp : source.getProperties()) {
350      if (has(pp.getName())) {
351        JsonElement te = get(pp.getName());
352        if (te.isJsonObject() && pp.getValue().isJsonObject()) {
353          ((JsonObject) te).merge((JsonObject) pp.getValue());
354        } else {
355          set(pp.getName(), pp.getValue());
356        }
357      } else {
358        add(pp.getName(), pp.getValue());
359      }
360    }
361  }
362
363
364  @Override
365  public String toString() {
366    StringBuilder b = new StringBuilder();
367    b.append("{ ");
368    boolean first = true;
369    for (JsonProperty p : properties) {
370      if (first) first = false; else b.append(", ");
371      b.append(p.toString());
372    }
373    b.append(" }");
374    return b.toString();
375  }
376
377  public boolean isExtraComma() {
378    return extraComma;
379  }
380
381  public void setExtraComma(boolean extraComma) {
382    this.extraComma = extraComma;
383  }
384  
385  
386}