001package org.hl7.fhir.r5.formats;
002
003/*
004  Copyright (c) 2011+, HL7, Inc.
005  All rights reserved.
006  
007  Redistribution and use in source and binary forms, with or without modification, 
008  are permitted provided that the following conditions are met:
009    
010   * Redistributions of source code must retain the above copyright notice, this 
011     list of conditions and the following disclaimer.
012   * Redistributions in binary form must reproduce the above copyright notice, 
013     this list of conditions and the following disclaimer in the documentation 
014     and/or other materials provided with the distribution.
015   * Neither the name of HL7 nor the names of its contributors may be used to 
016     endorse or promote products derived from this software without specific 
017     prior written permission.
018  
019  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028  POSSIBILITY OF SUCH DAMAGE.
029  
030 */
031
032
033
034import java.io.IOException;
035import java.io.OutputStreamWriter;
036import java.math.BigDecimal;
037import java.util.ArrayList;
038import java.util.Collections;
039import java.util.List;
040import java.util.Stack;
041
042
043public class JsonCreatorCanonical implements JsonCreator {
044
045  public class JsonCanValue {
046    String name;
047    private JsonCanValue(String name) {
048      this.name = name;  
049   }
050  }
051
052  private class JsonCanNumberValue extends JsonCanValue {
053    private String value;
054    private JsonCanNumberValue(String name, BigDecimal value) {
055      super(name);
056      this.value = canonicaliseDecimal(value.toPlainString());  
057    }
058  }
059
060  private class JsonCanPresentedNumberValue extends JsonCanValue {
061    private String value;
062    private JsonCanPresentedNumberValue(String name, String value) {
063      super(name);
064      this.value = canonicaliseDecimal(value);  
065    }
066    
067  }
068
069  private String canonicaliseDecimal(String v) {
070    return JsonNumberCanonicalizer.toCanonicalJson(v);
071  }
072  
073  private class JsonCanIntegerValue extends JsonCanValue {
074    private Integer value;
075    private JsonCanIntegerValue(String name, Integer value) {
076      super(name);
077      this.value = value;  
078    }
079  }
080
081  private class JsonCanBooleanValue extends JsonCanValue  {
082    private Boolean value;
083    private JsonCanBooleanValue(String name, Boolean value) {
084      super(name);
085      this.value = value;  
086    }
087  }
088
089  private class JsonCanStringValue extends JsonCanValue {
090    private String value;
091    private JsonCanStringValue(String name, String value) {
092      super(name);
093      this.value = value;  
094    }
095  }
096
097  private class JsonCanNullValue extends JsonCanValue  {
098    private JsonCanNullValue(String name) {
099      super(name);
100    }
101  }
102
103  public class JsonCanObject extends JsonCanValue {
104
105    boolean array;
106    List<JsonCanValue> children = new ArrayList<JsonCanValue>();
107    
108    public JsonCanObject(String name, boolean array) {
109      super(name);
110      this.array = array;
111    }
112
113    public void addProp(JsonCanValue obj) {
114      children.add(obj);
115    }
116  }
117
118  Stack<JsonCanObject> stack;
119  JsonCanObject root; 
120  JsonCreatorDirect jj;
121  String name;
122  
123  public JsonCreatorCanonical(OutputStreamWriter osw) {
124    stack = new Stack<JsonCreatorCanonical.JsonCanObject>();
125    jj = new JsonCreatorDirect(osw, false, false);
126    name = null;
127  }
128
129  private String takeName() {
130    String res = name;
131    name = null;
132    return res;
133  }
134
135  @Override
136  public void beginObject() throws IOException {
137    JsonCanObject obj = new JsonCanObject(takeName(), false);
138    if (stack.isEmpty())
139      root = obj;
140    else
141      stack.peek().addProp(obj);
142    stack.push(obj);
143  }
144
145  @Override
146  public void endObject() throws IOException {
147    stack.pop();
148  }
149
150  @Override
151  public void nullValue() throws IOException {
152    stack.peek().addProp(new JsonCanNullValue(takeName()));
153  }
154
155  @Override
156  public void name(String name) throws IOException {
157    this.name = name;
158  }
159
160  @Override
161  public void value(String value) throws IOException {
162    stack.peek().addProp(new JsonCanStringValue(takeName(), value));    
163  }
164
165  @Override
166  public void value(Boolean value) throws IOException {
167    stack.peek().addProp(new JsonCanBooleanValue(takeName(), value));    
168  }
169
170  @Override
171  public void value(BigDecimal value) throws IOException {
172    stack.peek().addProp(new JsonCanNumberValue(takeName(), value));    
173  }
174  @Override
175  public void valueNum(String value) throws IOException {
176    stack.peek().addProp(new JsonCanPresentedNumberValue(takeName(), value));    
177  }
178
179
180  @Override
181  public void value(Integer value) throws IOException {
182    stack.peek().addProp(new JsonCanIntegerValue(takeName(), value));    
183  }
184
185  @Override
186  public void beginArray() throws IOException {
187    JsonCanObject obj = new JsonCanObject(takeName(), true);
188    if (!stack.isEmpty())
189      stack.peek().addProp(obj);
190    stack.push(obj);
191    
192  }
193
194  @Override
195  public void endArray() throws IOException {
196    stack.pop();    
197  }
198
199  @Override
200  public void finish() throws IOException {
201    writeObject(root);
202  }
203
204  private void writeObject(JsonCanObject obj) throws IOException {
205    jj.beginObject();
206    List<String> names = new ArrayList<String>();
207    for (JsonCanValue v : obj.children) 
208      names.add(v.name);
209    Collections.sort(names);
210    for (String n : names) {
211      jj.name(n);
212      JsonCanValue v = getPropForName(n, obj.children);
213      if (v instanceof JsonCanNumberValue)
214        jj.valueNum(((JsonCanNumberValue) v).value);
215      else if (v instanceof JsonCanPresentedNumberValue)
216        jj.valueNum(((JsonCanPresentedNumberValue) v).value);
217      else if (v instanceof JsonCanIntegerValue)
218        jj.value(((JsonCanIntegerValue) v).value);
219      else if (v instanceof JsonCanBooleanValue)
220        jj.value(((JsonCanBooleanValue) v).value);
221      else if (v instanceof JsonCanStringValue)
222        jj.value(((JsonCanStringValue) v).value);
223      else if (v instanceof JsonCanNullValue)
224        jj.nullValue();
225      else if (v instanceof JsonCanObject) {
226        JsonCanObject o = (JsonCanObject) v;
227        if (o.array) 
228          writeArray(o);
229        else
230          writeObject(o);
231      } else
232        throw new Error("not possible");
233    }
234    jj.endObject();
235  }
236
237  private JsonCanValue getPropForName(String name, List<JsonCanValue> children) {
238    for (JsonCanValue child : children)
239      if (child.name.equals(name))
240        return child;
241    return null;
242  }
243
244  private void writeArray(JsonCanObject arr) throws IOException {
245    jj.beginArray();
246    for (JsonCanValue v : arr.children) { 
247      if (v instanceof JsonCanNumberValue)
248        jj.valueNum(((JsonCanNumberValue) v).value);
249      else if (v instanceof JsonCanPresentedNumberValue)
250        jj.valueNum(((JsonCanPresentedNumberValue) v).value);
251      else if (v instanceof JsonCanIntegerValue)
252          jj.value(((JsonCanIntegerValue) v).value);
253      else if (v instanceof JsonCanBooleanValue)
254        jj.value(((JsonCanBooleanValue) v).value);
255      else if (v instanceof JsonCanStringValue)
256        jj.value(((JsonCanStringValue) v).value);
257      else if (v instanceof JsonCanNullValue)
258        jj.nullValue();
259      else if (v instanceof JsonCanObject) {
260        JsonCanObject o = (JsonCanObject) v;
261        if (o.array) 
262          writeArray(o);
263        else
264          writeObject(o);
265      } else
266        throw new Error("not possible");
267    }
268    jj.endArray();    
269  }
270
271  @Override
272  public void comment(String content) {
273    // canonical JSON ignores comments    
274  }
275
276  @Override
277  public void link(String href) {
278    // not used
279  }
280       
281  @Override
282  public void anchor(String name) {
283    // not used
284  }
285
286  @Override
287  public void externalLink(String string) {
288    // not used
289  }
290
291  @Override
292  public boolean canElide() { return false; }
293
294  @Override
295  public void elide() {
296    // not used
297  }
298
299  @Override
300  public boolean isCanonical() {
301    return true;
302  }
303
304}