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