1
2
3
4
5
6 package dev.metaschema.schemagen.json.impl;
7
8 import com.fasterxml.jackson.core.JsonGenerator;
9 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
10 import com.fasterxml.jackson.databind.node.ObjectNode;
11
12 import java.io.IOException;
13 import java.util.LinkedHashMap;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.Objects;
17 import java.util.concurrent.ConcurrentHashMap;
18 import java.util.function.Supplier;
19
20 import dev.metaschema.core.configuration.IConfiguration;
21 import dev.metaschema.core.datatype.IDataTypeAdapter;
22 import dev.metaschema.core.model.IAssemblyDefinition;
23 import dev.metaschema.core.model.IAssemblyInstanceAbsolute;
24 import dev.metaschema.core.model.IAssemblyInstanceGrouped;
25 import dev.metaschema.core.model.IChoiceGroupInstance;
26 import dev.metaschema.core.model.IDefinition;
27 import dev.metaschema.core.model.IFieldDefinition;
28 import dev.metaschema.core.model.IFieldInstanceAbsolute;
29 import dev.metaschema.core.model.IFieldInstanceGrouped;
30 import dev.metaschema.core.model.IFlagDefinition;
31 import dev.metaschema.core.model.IFlagInstance;
32 import dev.metaschema.core.model.IModelDefinition;
33 import dev.metaschema.core.model.IModelInstanceAbsolute;
34 import dev.metaschema.core.model.IModule;
35 import dev.metaschema.core.model.INamedModelInstanceGrouped;
36 import dev.metaschema.core.model.IValuedDefinition;
37 import dev.metaschema.core.model.constraint.IAllowedValue;
38 import dev.metaschema.core.qname.IEnhancedQName;
39 import dev.metaschema.core.util.ObjectUtils;
40 import dev.metaschema.schemagen.AbstractGenerationState;
41 import dev.metaschema.schemagen.IGenerationState;
42 import dev.metaschema.schemagen.SchemaGenerationFeature;
43 import edu.umd.cs.findbugs.annotations.NonNull;
44 import edu.umd.cs.findbugs.annotations.Nullable;
45
46
47
48
49
50
51
52
53 public class JsonGenerationState
54 extends AbstractGenerationState<JsonGenerator, JsonDatatypeManager>
55 implements IJsonGenerationState {
56 @NonNull
57 private final JsonNodeFactory jsonNodeFactory = new JsonNodeFactory(true);
58 @NonNull
59 private final Map<IValuedDefinition, IDataTypeJsonSchema> definitionValueToDataTypeSchemaMap
60 = new ConcurrentHashMap<>();
61 @NonNull
62 private final Map<IDataTypeAdapter<?>, IDataTypeJsonSchema> dataTypeToSchemaMap = new ConcurrentHashMap<>();
63
64 @NonNull
65 private final Map<String, IJsonSchemaDefinable> definitionNameToJsonSchemaMap = new ConcurrentHashMap<>();
66
67 @NonNull
68 private final Map<IDefinition, Map<IEnhancedQName, IJsonSchemaDefinition>> definitionToJsonKeyToJsonSchemaMap
69 = new ConcurrentHashMap<>();
70
71 @NonNull
72 private final Map<GroupedDefinition,
73 Map<IEnhancedQName, IJsonSchemaPropertyGrouped>> groupedInstanceToJsonKeyToJsonSchemaMap
74 = new ConcurrentHashMap<>();
75
76
77
78
79
80
81
82
83
84
85
86 public JsonGenerationState(
87 @NonNull IModule module,
88 @NonNull JsonGenerator writer,
89 @NonNull IConfiguration<SchemaGenerationFeature<?>> configuration) {
90 super(module, writer, configuration, new JsonDatatypeManager());
91 }
92
93 @NonNull
94 private <T extends IJsonSchemaDefinition> T addToCache(
95 @NonNull IDefinition definition,
96 @Nullable IEnhancedQName jsonKeyName,
97 @NonNull Supplier<T> supplier) {
98
99 Map<IEnhancedQName, IJsonSchemaDefinition> jsonKeyMap
100 = definitionToJsonKeyToJsonSchemaMap.computeIfAbsent(definition, (key) -> new LinkedHashMap<>());
101
102 @SuppressWarnings("unchecked")
103 T retval = (T) jsonKeyMap.computeIfAbsent(jsonKeyName, (key) -> supplier.get());
104
105 assert definition.equals(retval.getDefinition());
106
107 if (!isInline(definition)) {
108
109 IJsonSchemaDefinable newSchema
110 = definitionNameToJsonSchemaMap.computeIfAbsent(retval.getDefinitionName(), (key) -> retval);
111
112 assert newSchema.equals(retval) : "Duplicate JSON definition name: "
113 + retval.getDefinitionName();
114 }
115
116 return retval;
117 }
118
119 @NonNull
120 private <T extends IJsonSchemaPropertyGrouped> T addToCache(
121 @NonNull INamedModelInstanceGrouped instance,
122 @Nullable IEnhancedQName jsonKeyName,
123 @NonNull Supplier<T> supplier) {
124 GroupedDefinition grouped = new GroupedDefinition(instance);
125
126
127 Map<IEnhancedQName, IJsonSchemaPropertyGrouped> jsonKeyMap
128 = groupedInstanceToJsonKeyToJsonSchemaMap.computeIfAbsent(grouped, (key) -> new LinkedHashMap<>());
129
130 @SuppressWarnings("unchecked")
131 T retval = (T) jsonKeyMap.computeIfAbsent(jsonKeyName, (key) -> supplier.get());
132
133 assert grouped.equals(new GroupedDefinition(retval.getInstance()));
134
135 if (!isInline(instance.getDefinition())) {
136
137 IJsonSchemaDefinable newSchema
138 = definitionNameToJsonSchemaMap.computeIfAbsent(retval.getDefinitionName(), (key) -> retval);
139
140 assert newSchema.equals(retval) : "Duplicate JSON definition name: "
141 + retval.getDefinitionName();
142 }
143 return retval;
144 }
145
146 private static class GroupedDefinition {
147 private final IModelDefinition definition;
148 private final String disciminatorProperty;
149 private final String disciminatorValue;
150
151 public GroupedDefinition(@NonNull INamedModelInstanceGrouped instance) {
152 this.definition = instance.getDefinition();
153 this.disciminatorProperty = instance.getParentContainer().getJsonDiscriminatorProperty();
154 this.disciminatorValue = instance.getEffectiveDisciminatorValue();
155 }
156
157 @Override
158 public int hashCode() {
159 return Objects.hash(definition, disciminatorProperty, disciminatorValue);
160 }
161
162 @Override
163 public boolean equals(Object obj) {
164 if (this == obj) {
165 return true;
166 }
167 if (obj == null) {
168 return false;
169 }
170 if (getClass() != obj.getClass()) {
171 return false;
172 }
173 GroupedDefinition other = (GroupedDefinition) obj;
174 return Objects.equals(definition, other.definition)
175 && Objects.equals(disciminatorProperty, other.disciminatorProperty)
176 && Objects.equals(disciminatorValue, other.disciminatorValue);
177 }
178 }
179
180 @Override
181 public IJsonSchemaDefinitionAssembly getAssemblyDefinition(
182 IAssemblyDefinition definition,
183 IEnhancedQName jsonKeyName) {
184 return addToCache(definition, jsonKeyName, () -> new JsonSchemaDefinitionAssembly(definition, jsonKeyName, this));
185 }
186
187 @Override
188 public IJsonSchemaDefinitionField getFieldDefinition(IFieldDefinition definition, IEnhancedQName jsonKeyName) {
189 return addToCache(definition, jsonKeyName, () -> new JsonSchemaDefinitionField(definition, jsonKeyName, this));
190 }
191
192 @Override
193 public IJsonSchemaDefinition getFlagDefinition(IFlagDefinition definition) {
194 return addToCache(definition, null, () -> new JsonSchemaDefinitionFlag(definition, this));
195 }
196
197 @Override
198 public IJsonSchemaPropertyFlag getJsonSchemaPropertyFlag(IFlagInstance instance) {
199 return new JsonSchemaPropertyFlag(instance, this);
200 }
201
202 @Override
203 public IJsonSchemaPropertyNamed getJsonSchemaPropertyModel(@NonNull IModelInstanceAbsolute instance) {
204 IJsonSchemaPropertyNamed retval;
205 if (instance instanceof IAssemblyInstanceAbsolute) {
206 retval = new JsonSchemaPropertyAssembly((IAssemblyInstanceAbsolute) instance, this);
207 } else if (instance instanceof IFieldInstanceAbsolute) {
208 retval = new JsonSchemaPropertyField((IFieldInstanceAbsolute) instance, this);
209 } else if (instance instanceof IChoiceGroupInstance) {
210 retval = new JsonSchemaPropertyChoiceGroup((IChoiceGroupInstance) instance, this);
211 } else {
212 throw new UnsupportedOperationException("Unsupported property type: " + instance.getClass());
213 }
214 return retval;
215 }
216
217 @Override
218 public IJsonSchemaPropertyGrouped getJsonSchemaPropertyGrouped(INamedModelInstanceGrouped instance) {
219 return addToCache(instance, null, () -> newJsonSchemaPropertyGrouped(instance));
220 }
221
222 private IJsonSchemaPropertyGrouped newJsonSchemaPropertyGrouped(INamedModelInstanceGrouped instance) {
223 IJsonSchemaPropertyGrouped retval;
224 if (instance instanceof IAssemblyInstanceGrouped) {
225 retval = new JsonSchemaPropertyGroupedAssembly((IAssemblyInstanceGrouped) instance, this);
226 } else if (instance instanceof IFieldInstanceGrouped) {
227 retval = new JsonSchemaPropertyGroupedField((IFieldInstanceGrouped) instance, this);
228 } else {
229 throw new UnsupportedOperationException("Unsupported property type: " + instance.getClass());
230 }
231 return retval;
232 }
233
234 @Override
235 @NonNull
236 public IDataTypeJsonSchema getSchema(@NonNull IDataTypeAdapter<?> datatype) {
237 IDataTypeJsonSchema retval = dataTypeToSchemaMap.get(datatype);
238 if (retval == null) {
239 retval = new DataTypeJsonSchema(
240 getDatatypeManager().getTypeNameForDatatype(datatype),
241 datatype);
242 dataTypeToSchemaMap.put(datatype, retval);
243 }
244 return retval;
245 }
246
247 @Override
248 public void generateDataTypeDefinitions(@NonNull ObjectNode definitionsNode) {
249 getDatatypeManager().generateDatatypeDefinitions(definitionsNode);
250 }
251
252 @Override
253 public JsonNodeFactory getJsonNodeFactory() {
254 return jsonNodeFactory;
255 }
256
257 @Override
258 @NonNull
259 public IDataTypeJsonSchema getDataTypeSchemaForDefinition(@NonNull IValuedDefinition definition) {
260 IDataTypeJsonSchema retval = definitionValueToDataTypeSchemaMap.get(definition);
261 if (retval == null) {
262 AllowedValueCollection allowedValuesCollection = getContextIndependentEnumeratedValues(definition);
263 List<IAllowedValue> allowedValues = allowedValuesCollection.getValues();
264
265 IDataTypeAdapter<?> dataTypeAdapter = definition.getJavaTypeAdapter();
266
267
268 retval = getSchema(dataTypeAdapter);
269 if (!allowedValues.isEmpty()) {
270
271 retval = new DataTypeRestrictionDefinitionJsonSchema(definition, allowedValuesCollection, this);
272 }
273 definitionValueToDataTypeSchemaMap.put(definition, retval);
274 }
275 return retval;
276 }
277
278 @Override
279 @SuppressWarnings("PMD.UseObjectForClearerAPI")
280 public String generateJsonSchemaDefinitionName(
281 @NonNull IDefinition definition,
282 @Nullable String jsonKeyFlagName,
283 @Nullable String discriminatorProperty,
284 @Nullable String discriminatorValue,
285 @Nullable String suffix) {
286 StringBuilder builder = new StringBuilder();
287 if (jsonKeyFlagName != null) {
288 builder
289 .append(IGenerationState.toCamelCase(jsonKeyFlagName))
290 .append("JsonKey");
291 }
292
293 if (discriminatorProperty != null || discriminatorValue != null) {
294 builder
295 .append(IGenerationState.toCamelCase(ObjectUtils.requireNonNull(discriminatorProperty)))
296 .append(IGenerationState.toCamelCase(ObjectUtils.requireNonNull(discriminatorValue)))
297 .append("Choice");
298 }
299
300 if (suffix != null) {
301 builder.append(suffix);
302 }
303 return getTypeNameForDefinition(definition, builder.toString());
304 }
305
306
307
308
309
310
311
312
313
314 public void writeObject(ObjectNode schemaNode) throws IOException {
315 getWriter().writeObject(schemaNode);
316 }
317
318
319
320
321
322
323
324 public void writeStartObject() throws IOException {
325 getWriter().writeStartObject();
326 }
327
328
329
330
331
332
333
334 public void writeEndObject() throws IOException {
335 getWriter().writeEndObject();
336 }
337
338
339
340
341
342
343
344
345
346
347
348 public void writeField(String fieldName, String value) throws IOException {
349 getWriter().writeStringField(fieldName, value);
350
351 }
352
353
354
355
356
357
358
359
360
361
362
363 public void writeField(String fieldName, ObjectNode obj) throws IOException {
364 JsonGenerator writer = getWriter();
365
366 writer.writeFieldName(fieldName);
367 writer.writeTree(obj);
368 }
369
370 @Override
371 public void flushWriter() throws IOException {
372 getWriter().flush();
373 }
374 }