1
2
3
4
5
6 package gov.nist.secauto.metaschema.databind.io.json;
7
8 import com.fasterxml.jackson.core.JsonGenerator;
9
10 import gov.nist.secauto.metaschema.core.model.IBoundObject;
11 import gov.nist.secauto.metaschema.core.model.JsonGroupAsBehavior;
12 import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly;
13 import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelComplex;
14 import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelFieldComplex;
15 import gov.nist.secauto.metaschema.databind.model.IBoundFieldValue;
16 import gov.nist.secauto.metaschema.databind.model.IBoundInstance;
17 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceFlag;
18 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModel;
19 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelAssembly;
20 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelChoiceGroup;
21 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelFieldComplex;
22 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelFieldScalar;
23 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedAssembly;
24 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedField;
25 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedNamed;
26 import gov.nist.secauto.metaschema.databind.model.IBoundProperty;
27 import gov.nist.secauto.metaschema.databind.model.info.AbstractModelInstanceWriteHandler;
28 import gov.nist.secauto.metaschema.databind.model.info.IFeatureComplexItemValueHandler;
29 import gov.nist.secauto.metaschema.databind.model.info.IFeatureScalarItemValueHandler;
30 import gov.nist.secauto.metaschema.databind.model.info.IItemWriteHandler;
31 import gov.nist.secauto.metaschema.databind.model.info.IModelInstanceCollectionInfo;
32
33 import java.io.IOException;
34 import java.util.List;
35
36 import edu.umd.cs.findbugs.annotations.NonNull;
37
38 public class MetaschemaJsonWriter implements IJsonWritingContext, IItemWriteHandler {
39 @NonNull
40 private final JsonGenerator generator;
41
42
43
44
45
46
47
48
49 public MetaschemaJsonWriter(@NonNull JsonGenerator generator) {
50 this.generator = generator;
51 }
52
53 @Override
54 public JsonGenerator getWriter() {
55 return generator;
56 }
57
58
59
60
61
62 @Override
63 public void write(
64 @NonNull IBoundDefinitionModelComplex definition,
65 @NonNull IBoundObject item) throws IOException {
66 definition.writeItem(item, this);
67 }
68
69
70
71
72
73 private <T> void writeInstance(
74 @NonNull IBoundProperty<T> instance,
75 @NonNull IBoundObject parentItem) throws IOException {
76 @SuppressWarnings("unchecked") T value = (T) instance.getValue(parentItem);
77 if (value != null && !value.equals(instance.getResolvedDefaultValue())) {
78 generator.writeFieldName(instance.getJsonName());
79 instance.writeItem(value, this);
80 }
81 }
82
83 private <T> void writeModelInstance(
84 @NonNull IBoundInstanceModel<T> instance,
85 @NonNull Object parentItem) throws IOException {
86 Object value = instance.getValue(parentItem);
87 if (value != null) {
88
89
90
91 IModelInstanceCollectionInfo<T> collectionInfo = instance.getCollectionInfo();
92 if (!collectionInfo.isEmpty(value)) {
93 generator.writeFieldName(instance.getJsonName());
94 collectionInfo.writeItems(new ModelInstanceWriteHandler<>(instance), value);
95 }
96 }
97 }
98
99 private void writeFieldValue(@NonNull IBoundFieldValue fieldValue, @NonNull Object parentItem) throws IOException {
100 Object item = fieldValue.getValue(parentItem);
101
102
103 IBoundInstanceFlag jsonValueKey = fieldValue.getParentFieldDefinition().getJsonValueKeyFlagInstance();
104 if (item == null) {
105 if (jsonValueKey != null) {
106 item = fieldValue.getDefaultValue();
107 }
108 } else if (item.equals(fieldValue.getResolvedDefaultValue())) {
109
110 item = null;
111 }
112
113 if (item != null) {
114
115
116
117
118 String valueKeyName;
119 if (jsonValueKey != null) {
120 Object keyValue = jsonValueKey.getValue(parentItem);
121 if (keyValue == null) {
122 throw new IOException(String.format("Null value for json-value-key for definition '%s'",
123 jsonValueKey.getContainingDefinition().toCoordinates()));
124 }
125
126 valueKeyName = jsonValueKey.getJavaTypeAdapter().asString(keyValue);
127 } else {
128 valueKeyName = fieldValue.getParentFieldDefinition().getEffectiveJsonValueKeyName();
129 }
130 generator.writeFieldName(valueKeyName);
131
132
133 writeItemFieldValue(item, fieldValue);
134 }
135 }
136
137 @Override
138 public void writeItemFlag(Object item, IBoundInstanceFlag instance) throws IOException {
139 writeScalarItem(item, instance);
140 }
141
142 @Override
143 public void writeItemField(Object item, IBoundInstanceModelFieldScalar instance) throws IOException {
144 writeScalarItem(item, instance);
145 }
146
147 @Override
148 public void writeItemField(IBoundObject item, IBoundInstanceModelFieldComplex instance) throws IOException {
149 writeModelObject(
150 instance,
151 item,
152 this::writeObjectProperties);
153 }
154
155 @Override
156 public void writeItemField(IBoundObject item, IBoundInstanceModelGroupedField instance) throws IOException {
157 writeGroupedModelObject(
158 instance,
159 item,
160 (parent, handler) -> {
161 writeDiscriminatorProperty(handler);
162 writeObjectProperties(parent, handler);
163 });
164 }
165
166 @Override
167 public void writeItemField(IBoundObject item, IBoundDefinitionModelFieldComplex definition) throws IOException {
168 writeDefinitionObject(
169 definition,
170 item,
171 (ObjectWriter<IBoundDefinitionModelFieldComplex>) this::writeObjectProperties);
172 }
173
174 @Override
175 public void writeItemFieldValue(Object item, IBoundFieldValue fieldValue) throws IOException {
176 fieldValue.getJavaTypeAdapter().writeJsonValue(item, generator);
177 }
178
179 @Override
180 public void writeItemAssembly(IBoundObject item, IBoundInstanceModelAssembly instance) throws IOException {
181 writeModelObject(instance, item, this::writeObjectProperties);
182 }
183
184 @Override
185 public void writeItemAssembly(IBoundObject item, IBoundInstanceModelGroupedAssembly instance) throws IOException {
186 writeGroupedModelObject(
187 instance,
188 item,
189 (parent, handler) -> {
190 writeDiscriminatorProperty(handler);
191 writeObjectProperties(parent, handler);
192 });
193 }
194
195 @Override
196 public void writeItemAssembly(IBoundObject item, IBoundDefinitionModelAssembly definition) throws IOException {
197 writeDefinitionObject(definition, item, this::writeObjectProperties);
198 }
199
200 @Override
201 public void writeChoiceGroupItem(IBoundObject item, IBoundInstanceModelChoiceGroup instance) throws IOException {
202 IBoundInstanceModelGroupedNamed actualInstance = instance.getItemInstance(item);
203 assert actualInstance != null;
204 actualInstance.writeItem(item, this);
205 }
206
207
208
209
210
211
212
213
214
215
216
217 private void writeScalarItem(@NonNull Object item, @NonNull IFeatureScalarItemValueHandler handler)
218 throws IOException {
219 handler.getJavaTypeAdapter().writeJsonValue(item, generator);
220 }
221
222 private <T extends IBoundInstanceModelGroupedNamed> void writeDiscriminatorProperty(
223 @NonNull T instance) throws IOException {
224
225 IBoundInstanceModelChoiceGroup choiceGroup = instance.getParentContainer();
226
227
228 String discriminatorProperty = choiceGroup.getJsonDiscriminatorProperty();
229 String discriminatorValue = instance.getEffectiveDisciminatorValue();
230
231 generator.writeStringField(discriminatorProperty, discriminatorValue);
232 }
233
234 private <T extends IFeatureComplexItemValueHandler> void writeObjectProperties(
235 @NonNull IBoundObject parent,
236 @NonNull T handler) throws IOException {
237 for (IBoundProperty<?> property : handler.getJsonProperties().values()) {
238 assert property != null;
239
240 if (property instanceof IBoundInstanceModel) {
241 writeModelInstance((IBoundInstanceModel<?>) property, parent);
242 } else if (property instanceof IBoundInstance) {
243 writeInstance(property, parent);
244 } else {
245 writeFieldValue((IBoundFieldValue) property, parent);
246 }
247 }
248 }
249
250 private <T extends IFeatureComplexItemValueHandler> void writeDefinitionObject(
251 @NonNull T handler,
252 @NonNull IBoundObject parent,
253 @NonNull ObjectWriter<T> propertyWriter) throws IOException {
254 generator.writeStartObject();
255
256 propertyWriter.accept(parent, handler);
257 generator.writeEndObject();
258 }
259
260 private <T extends IFeatureComplexItemValueHandler & IBoundInstanceModel<IBoundObject>>
261 void writeModelObject(
262 @NonNull T handler,
263 @NonNull IBoundObject parent,
264 @NonNull ObjectWriter<T> propertyWriter) throws IOException {
265 generator.writeStartObject();
266
267 IBoundInstanceFlag jsonKey = handler.getItemJsonKey(parent);
268 if (jsonKey != null) {
269 Object keyValue = jsonKey.getValue(parent);
270 if (keyValue == null) {
271 throw new IOException(String.format("Null value for json-key for definition '%s'",
272 jsonKey.getContainingDefinition().toCoordinates()));
273 }
274
275
276 String key = jsonKey.getJavaTypeAdapter().asString(keyValue);
277 generator.writeFieldName(key);
278
279
280 generator.writeStartObject();
281 }
282
283 propertyWriter.accept(parent, handler);
284
285 if (jsonKey != null) {
286
287 generator.writeEndObject();
288 }
289 generator.writeEndObject();
290 }
291
292 private <T extends IFeatureComplexItemValueHandler & IBoundInstanceModelGroupedNamed> void writeGroupedModelObject(
293 @NonNull T handler,
294 @NonNull IBoundObject parent,
295 @NonNull ObjectWriter<T> propertyWriter) throws IOException {
296 generator.writeStartObject();
297
298 IBoundInstanceModelChoiceGroup choiceGroup = handler.getParentContainer();
299 IBoundInstanceFlag jsonKey = choiceGroup.getItemJsonKey(parent);
300 if (jsonKey != null) {
301 Object keyValue = jsonKey.getValue(parent);
302 if (keyValue == null) {
303 throw new IOException(String.format("Null value for json-key for definition '%s'",
304 jsonKey.getContainingDefinition().toCoordinates()));
305 }
306
307
308 String key = jsonKey.getJavaTypeAdapter().asString(keyValue);
309 generator.writeFieldName(key);
310
311
312 generator.writeStartObject();
313 }
314
315 propertyWriter.accept(parent, handler);
316
317 if (jsonKey != null) {
318
319 generator.writeEndObject();
320 }
321 generator.writeEndObject();
322 }
323
324
325
326
327
328
329
330 private class ModelInstanceWriteHandler<ITEM>
331 extends AbstractModelInstanceWriteHandler<ITEM> {
332 public ModelInstanceWriteHandler(
333 @NonNull IBoundInstanceModel<ITEM> instance) {
334 super(instance);
335 }
336
337 @Override
338 public void writeList(List<ITEM> items) throws IOException {
339 IBoundInstanceModel<ITEM> instance = getCollectionInfo().getInstance();
340
341 boolean writeArray = false;
342 if (JsonGroupAsBehavior.LIST.equals(instance.getJsonGroupAsBehavior())
343 || JsonGroupAsBehavior.SINGLETON_OR_LIST.equals(instance.getJsonGroupAsBehavior())
344 && items.size() > 1) {
345
346 writeArray = true;
347 generator.writeStartArray();
348 }
349
350 super.writeList(items);
351
352 if (writeArray) {
353
354 generator.writeEndArray();
355 }
356 }
357
358 @Override
359 public void writeItem(ITEM item) throws IOException {
360 IBoundInstanceModel<ITEM> instance = getInstance();
361 instance.writeItem(item, MetaschemaJsonWriter.this);
362 }
363 }
364 }