Change alignments in methods declaration
[netconf.git] / restconf / sal-rest-docgen / src / main / java / org / opendaylight / netconf / sal / rest / doc / impl / DefinitionGenerator.java
1 /*
2  * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.netconf.sal.rest.doc.impl;
9
10 import static org.opendaylight.netconf.sal.rest.doc.impl.BaseYangSwaggerGenerator.MODULE_NAME_SUFFIX;
11 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.CONFIG;
12 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.NAME_KEY;
13 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.POST_SUFFIX;
14 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.TOP;
15 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.XML_KEY;
16 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.XML_SUFFIX;
17 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.getAppropriateModelPrefix;
18
19 import com.fasterxml.jackson.databind.JsonNode;
20 import com.fasterxml.jackson.databind.node.ArrayNode;
21 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
22 import com.fasterxml.jackson.databind.node.ObjectNode;
23 import com.fasterxml.jackson.databind.node.TextNode;
24 import com.google.common.collect.Range;
25 import com.google.common.collect.RangeSet;
26 import com.mifmif.common.regex.Generex;
27 import java.io.IOException;
28 import java.math.BigDecimal;
29 import java.util.Collection;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Optional;
34 import java.util.Set;
35 import java.util.regex.Pattern;
36 import java.util.stream.Collectors;
37 import org.opendaylight.netconf.sal.rest.doc.impl.ApiDocServiceImpl.OAversion;
38 import org.opendaylight.yangtools.yang.common.Decimal64;
39 import org.opendaylight.yangtools.yang.common.QName;
40 import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
41 import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer;
42 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.ContainerLike;
47 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
49 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
50 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
51 import org.opendaylight.yangtools.yang.model.api.ElementCountConstraint;
52 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
53 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
54 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
55 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
56 import org.opendaylight.yangtools.yang.model.api.MandatoryAware;
57 import org.opendaylight.yangtools.yang.model.api.Module;
58 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
59 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
60 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
61 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
62 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
63 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
64 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition.Bit;
65 import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
66 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
67 import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition;
68 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
69 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition.EnumPair;
70 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
71 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
72 import org.opendaylight.yangtools.yang.model.api.type.Int16TypeDefinition;
73 import org.opendaylight.yangtools.yang.model.api.type.Int32TypeDefinition;
74 import org.opendaylight.yangtools.yang.model.api.type.Int64TypeDefinition;
75 import org.opendaylight.yangtools.yang.model.api.type.Int8TypeDefinition;
76 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
77 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
78 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
79 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
80 import org.opendaylight.yangtools.yang.model.api.type.RangeRestrictedTypeDefinition;
81 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
82 import org.opendaylight.yangtools.yang.model.api.type.Uint16TypeDefinition;
83 import org.opendaylight.yangtools.yang.model.api.type.Uint32TypeDefinition;
84 import org.opendaylight.yangtools.yang.model.api.type.Uint64TypeDefinition;
85 import org.opendaylight.yangtools.yang.model.api.type.Uint8TypeDefinition;
86 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
87 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
88 import org.slf4j.Logger;
89 import org.slf4j.LoggerFactory;
90
91 /**
92  * Generates JSON Schema for data defined in YANG. This class is not thread-safe.
93  */
94 public class DefinitionGenerator {
95
96     private static final Logger LOG = LoggerFactory.getLogger(DefinitionGenerator.class);
97
98     private static final String UNIQUE_ITEMS_KEY = "uniqueItems";
99     private static final String MAX_ITEMS = "maxItems";
100     private static final String MIN_ITEMS = "minItems";
101     private static final String MAX_LENGTH_KEY = "maxLength";
102     private static final String MIN_LENGTH_KEY = "minLength";
103     private static final String REQUIRED_KEY = "required";
104     private static final String REF_KEY = "$ref";
105     private static final String ITEMS_KEY = "items";
106     private static final String TYPE_KEY = "type";
107     private static final String PROPERTIES_KEY = "properties";
108     private static final String DESCRIPTION_KEY = "description";
109     private static final String ARRAY_TYPE = "array";
110     private static final String ENUM_KEY = "enum";
111     private static final String TITLE_KEY = "title";
112     private static final String DEFAULT_KEY = "default";
113     private static final String FORMAT_KEY = "format";
114     private static final String NAMESPACE_KEY = "namespace";
115     public static final String INPUT = "input";
116     public static final String INPUT_SUFFIX = "_input";
117     public static final String OUTPUT = "output";
118     public static final String OUTPUT_SUFFIX = "_output";
119     private static final String STRING_TYPE = "string";
120     private static final String OBJECT_TYPE = "object";
121     private static final String NUMBER_TYPE = "number";
122     private static final String INTEGER_TYPE = "integer";
123     private static final String INT32_FORMAT = "int32";
124     private static final String INT64_FORMAT = "int64";
125     private static final String BOOLEAN_TYPE = "boolean";
126     // Special characters used in automaton inside Generex.
127     // See https://www.brics.dk/automaton/doc/dk/brics/automaton/RegExp.html
128     private static final Pattern AUTOMATON_SPECIAL_CHARACTERS = Pattern.compile("[@&\"<>#~]");
129
130     private Module topLevelModule;
131
132     public DefinitionGenerator() {
133     }
134
135     /**
136      * Creates Json definitions from provided module according to swagger spec.
137      *
138      * @param module          - Yang module to be converted
139      * @param schemaContext   - SchemaContext of all Yang files used by Api Doc
140      * @param definitionNames - Store for definition names
141      * @return ObjectNode containing data used for creating examples and definitions in Api Doc
142      * @throws IOException if I/O operation fails
143      */
144
145
146     public ObjectNode convertToJsonSchema(final Module module, final EffectiveModelContext schemaContext,
147             final ObjectNode definitions, final DefinitionNames definitionNames,  final OAversion oaversion,
148             final boolean isForSingleModule) throws IOException {
149         topLevelModule = module;
150
151         processIdentities(module, definitions, definitionNames, schemaContext);
152         processContainersAndLists(module, definitions, definitionNames, schemaContext, oaversion);
153         processRPCs(module, definitions, definitionNames, schemaContext, oaversion);
154
155         if (isForSingleModule) {
156             processModule(module, definitions, definitionNames, schemaContext, oaversion);
157         }
158
159         return definitions;
160     }
161
162     public ObjectNode convertToJsonSchema(final Module module, final EffectiveModelContext schemaContext,
163             final DefinitionNames definitionNames, final OAversion oaversion, final boolean isForSingleModule)
164             throws IOException {
165         final ObjectNode definitions = JsonNodeFactory.instance.objectNode();
166         if (isForSingleModule) {
167             definitionNames.addUnlinkedName(module.getName() + MODULE_NAME_SUFFIX);
168         }
169         return convertToJsonSchema(module, schemaContext, definitions, definitionNames, oaversion, isForSingleModule);
170     }
171
172     private void processModule(final Module module, final ObjectNode definitions, final DefinitionNames definitionNames,
173             final EffectiveModelContext schemaContext, final OAversion oaversion) {
174         final ObjectNode definition = JsonNodeFactory.instance.objectNode();
175         final ObjectNode properties = JsonNodeFactory.instance.objectNode();
176         final ArrayNode required = JsonNodeFactory.instance.arrayNode();
177         final String moduleName = module.getName();
178         final String definitionName = moduleName + MODULE_NAME_SUFFIX;
179         final SchemaInferenceStack stack = SchemaInferenceStack.of(schemaContext);
180         for (final DataSchemaNode node : module.getChildNodes()) {
181             stack.enterSchemaTree(node.getQName());
182             final String localName = node.getQName().getLocalName();
183             if (node.isConfiguration()) {
184                 if (node instanceof ContainerSchemaNode || node instanceof ListSchemaNode) {
185                     for (final DataSchemaNode childNode : ((DataNodeContainer) node).getChildNodes()) {
186                         final ObjectNode childNodeProperties = JsonNodeFactory.instance.objectNode();
187
188                         final String ref = getAppropriateModelPrefix(oaversion)
189                                 + moduleName + CONFIG
190                                 + "_" + localName
191                                 + definitionNames.getDiscriminator(node);
192
193                         if (node instanceof ListSchemaNode) {
194                             childNodeProperties.put(TYPE_KEY, ARRAY_TYPE);
195                             final ObjectNode items = JsonNodeFactory.instance.objectNode();
196                             items.put(REF_KEY, ref);
197                             childNodeProperties.set(ITEMS_KEY, items);
198                             childNodeProperties.put(DESCRIPTION_KEY, childNode.getDescription().orElse(""));
199                             childNodeProperties.put(TITLE_KEY, localName + CONFIG);
200                         } else {
201                          /*
202                             Description can't be added, because nothing allowed alongside $ref.
203                             allOf is not an option, because ServiceNow can't parse it.
204                           */
205                             childNodeProperties.put(REF_KEY, ref);
206                         }
207                         //add module name prefix to property name, when ServiceNow can process colons
208                         properties.set(localName, childNodeProperties);
209                     }
210                 } else if (node instanceof LeafSchemaNode) {
211                     /*
212                         Add module name prefix to property name, when ServiceNow can process colons(second parameter
213                         of processLeafNode).
214                      */
215                     processLeafNode((LeafSchemaNode) node, localName, properties, required, stack,
216                             definitions, definitionNames, oaversion);
217                 }
218             }
219             stack.exit();
220         }
221         definition.put(TITLE_KEY, definitionName);
222         definition.put(TYPE_KEY, OBJECT_TYPE);
223         definition.set(PROPERTIES_KEY, properties);
224         definition.put(DESCRIPTION_KEY, module.getDescription().orElse(""));
225         setRequiredIfNotEmpty(definition, required);
226
227         definitions.set(definitionName, definition);
228     }
229
230     private void processContainersAndLists(final Module module, final ObjectNode definitions,
231             final DefinitionNames definitionNames, final EffectiveModelContext schemaContext,
232             final OAversion oaversion)  throws IOException {
233         final String moduleName = module.getName();
234         final SchemaInferenceStack stack = SchemaInferenceStack.of(schemaContext);
235         for (final DataSchemaNode childNode : module.getChildNodes()) {
236             stack.enterSchemaTree(childNode.getQName());
237             // For every container and list in the module
238             if (childNode instanceof ContainerSchemaNode || childNode instanceof ListSchemaNode) {
239                 if (childNode.isConfiguration()) {
240                     processDataNodeContainer((DataNodeContainer) childNode, moduleName, definitions, definitionNames,
241                             true, stack, oaversion);
242                 }
243                 processDataNodeContainer((DataNodeContainer) childNode, moduleName, definitions, definitionNames,
244                         false, stack, oaversion);
245                 processActionNodeContainer(childNode, moduleName, definitions, definitionNames, stack, oaversion);
246             }
247             stack.exit();
248         }
249     }
250
251     private void processActionNodeContainer(final DataSchemaNode childNode, final String moduleName,
252             final ObjectNode definitions, final DefinitionNames definitionNames, final SchemaInferenceStack stack,
253             final OAversion oaversion) throws IOException {
254         for (final ActionDefinition actionDef : ((ActionNodeContainer) childNode).getActions()) {
255             stack.enterSchemaTree(actionDef.getQName());
256             processOperations(actionDef, moduleName, definitions, definitionNames, stack, oaversion);
257             stack.exit();
258         }
259     }
260
261     private void processRPCs(final Module module, final ObjectNode definitions, final DefinitionNames definitionNames,
262             final EffectiveModelContext schemaContext, final OAversion oaversion) throws IOException {
263         final String moduleName = module.getName();
264         final SchemaInferenceStack stack = SchemaInferenceStack.of(schemaContext);
265         for (final RpcDefinition rpcDefinition : module.getRpcs()) {
266             stack.enterSchemaTree(rpcDefinition.getQName());
267             processOperations(rpcDefinition, moduleName, definitions, definitionNames, stack, oaversion);
268             stack.exit();
269         }
270     }
271
272     private void processOperations(final OperationDefinition operationDef, final String parentName,
273             final ObjectNode definitions, final DefinitionNames definitionNames,
274             final SchemaInferenceStack stack, final OAversion oaversion) throws IOException {
275         final String operationName = operationDef.getQName().getLocalName();
276         processOperationInputOutput(operationDef.getInput(), operationName, parentName, true, definitions,
277                 definitionNames, stack, oaversion);
278         processOperationInputOutput(operationDef.getOutput(), operationName, parentName, false, definitions,
279                 definitionNames, stack, oaversion);
280     }
281
282     private void processOperationInputOutput(final ContainerLike container, final String operationName,
283             final String parentName, final boolean isInput, final ObjectNode definitions,
284             final DefinitionNames definitionNames, final SchemaInferenceStack stack, final OAversion oaversion)
285             throws IOException {
286         stack.enterSchemaTree(container.getQName());
287         if (!container.getChildNodes().isEmpty()) {
288             final String filename = parentName + "_" + operationName + (isInput ? INPUT_SUFFIX : OUTPUT_SUFFIX);
289             final ObjectNode childSchema = JsonNodeFactory.instance.objectNode();
290             processChildren(childSchema, container.getChildNodes(), parentName, definitions, definitionNames,
291                     false, stack, oaversion);
292
293             childSchema.put(TYPE_KEY, OBJECT_TYPE);
294             final ObjectNode xml = JsonNodeFactory.instance.objectNode();
295             xml.put(NAME_KEY, isInput ? INPUT : OUTPUT);
296             childSchema.set(XML_KEY, xml);
297             childSchema.put(TITLE_KEY, filename);
298             final String discriminator =
299                     definitionNames.pickDiscriminator(container, List.of(filename, filename + TOP));
300             definitions.set(filename + discriminator, childSchema);
301
302             processTopData(filename, discriminator, definitions, container, oaversion);
303         }
304         stack.exit();
305     }
306
307     private static ObjectNode processTopData(final String filename, final String discriminator,
308             final ObjectNode definitions, final SchemaNode schemaNode, final OAversion oaversion) {
309         final ObjectNode dataNodeProperties = JsonNodeFactory.instance.objectNode();
310         final String name = filename + discriminator;
311         final String ref = getAppropriateModelPrefix(oaversion) + name;
312         final String topName = filename + TOP;
313
314         if (schemaNode instanceof ListSchemaNode) {
315             dataNodeProperties.put(TYPE_KEY, ARRAY_TYPE);
316             final ObjectNode items = JsonNodeFactory.instance.objectNode();
317             items.put(REF_KEY, ref);
318             dataNodeProperties.set(ITEMS_KEY, items);
319             dataNodeProperties.put(DESCRIPTION_KEY, schemaNode.getDescription().orElse(""));
320         } else {
321              /*
322                 Description can't be added, because nothing allowed alongside $ref.
323                 allOf is not an option, because ServiceNow can't parse it.
324               */
325             dataNodeProperties.put(REF_KEY, ref);
326         }
327
328         final ObjectNode properties = JsonNodeFactory.instance.objectNode();
329         /*
330             Add module name prefix to property name, when needed, when ServiceNow can process colons,
331             use RestDocGenUtil#resolveNodesName for creating property name
332          */
333         properties.set(schemaNode.getQName().getLocalName(), dataNodeProperties);
334         final ObjectNode finalChildSchema = JsonNodeFactory.instance.objectNode();
335         finalChildSchema.put(TYPE_KEY, OBJECT_TYPE);
336         finalChildSchema.set(PROPERTIES_KEY, properties);
337         finalChildSchema.put(TITLE_KEY, topName);
338
339
340         definitions.set(topName + discriminator, finalChildSchema);
341
342         return dataNodeProperties;
343     }
344
345     /**
346      * Processes the 'identity' statement in a yang model and maps it to a 'model' in the Swagger JSON spec.
347      * @param module          The module from which the identity stmt will be processed
348      * @param definitions     The ObjectNode in which the parsed identity will be put as a 'model' obj
349      * @param definitionNames Store for definition names
350      */
351     private static void processIdentities(final Module module, final ObjectNode definitions,
352             final DefinitionNames definitionNames, final EffectiveModelContext context) {
353         final String moduleName = module.getName();
354         final Collection<? extends IdentitySchemaNode> idNodes = module.getIdentities();
355         LOG.debug("Processing Identities for module {} . Found {} identity statements", moduleName, idNodes.size());
356
357         for (final IdentitySchemaNode idNode : idNodes) {
358             final ObjectNode identityObj = buildIdentityObject(idNode, context);
359             final String idName = idNode.getQName().getLocalName();
360             final String discriminator = definitionNames.pickDiscriminator(idNode, List.of(idName));
361             final String name = idName + discriminator;
362             definitions.set(name, identityObj);
363         }
364     }
365
366     private static void populateEnumWithDerived(final Collection<? extends IdentitySchemaNode> derivedIds,
367             final ArrayNode enumPayload, final EffectiveModelContext context) {
368         for (final IdentitySchemaNode derivedId : derivedIds) {
369             enumPayload.add(derivedId.getQName().getLocalName());
370             populateEnumWithDerived(context.getDerivedIdentities(derivedId), enumPayload, context);
371         }
372     }
373
374     private ObjectNode processDataNodeContainer(final DataNodeContainer dataNode, final String parentName,
375             final ObjectNode definitions, final DefinitionNames definitionNames, final boolean isConfig,
376             final SchemaInferenceStack stack, final OAversion oaversion) throws IOException {
377         if (dataNode instanceof ListSchemaNode || dataNode instanceof ContainerSchemaNode) {
378             final Collection<? extends DataSchemaNode> containerChildren = dataNode.getChildNodes();
379             final SchemaNode schemaNode = (SchemaNode) dataNode;
380             final String localName = schemaNode.getQName().getLocalName();
381             final ObjectNode childSchema = JsonNodeFactory.instance.objectNode();
382             final String nameAsParent = parentName + "_" + localName;
383             final ObjectNode properties =
384                     processChildren(childSchema, containerChildren, parentName + "_" + localName, definitions,
385                             definitionNames, isConfig, stack, oaversion);
386
387             final String nodeName = parentName + (isConfig ? CONFIG : "") + "_" + localName;
388             final String postNodeName = parentName + CONFIG + "_" + localName + POST_SUFFIX;
389             final String postXmlNodeName = postNodeName + XML_SUFFIX;
390             final String parentNameConfigLocalName = parentName + CONFIG + "_" + localName;
391
392             final String description = schemaNode.getDescription().orElse("");
393             final String discriminator;
394
395             if (!definitionNames.isListedNode(schemaNode)) {
396                 final List<String> names = List.of(parentNameConfigLocalName,
397                         parentNameConfigLocalName + TOP,
398                         nameAsParent,
399                         nameAsParent + TOP,
400                         postNodeName,
401                         postXmlNodeName);
402                 discriminator = definitionNames.pickDiscriminator(schemaNode, names);
403             } else {
404                 discriminator = definitionNames.getDiscriminator(schemaNode);
405             }
406
407             if (isConfig) {
408                 final ObjectNode postSchema = createPostJsonSchema(schemaNode, properties, postNodeName, description);
409                 String truePostNodeName = postNodeName + discriminator;
410                 definitions.set(truePostNodeName, postSchema);
411
412                 final ObjectNode postXmlSchema = JsonNodeFactory.instance.objectNode();
413                 postXmlSchema.put(REF_KEY, getAppropriateModelPrefix(oaversion) + truePostNodeName);
414                 definitions.set(postXmlNodeName + discriminator, postXmlSchema);
415             }
416
417             childSchema.put(TYPE_KEY, OBJECT_TYPE);
418             childSchema.set(PROPERTIES_KEY, properties);
419             childSchema.put(TITLE_KEY, nodeName);
420             childSchema.put(DESCRIPTION_KEY, description);
421
422             final String defName = nodeName + discriminator;
423             childSchema.set(XML_KEY, buildXmlParameter(schemaNode));
424             definitions.set(defName, childSchema);
425
426             return processTopData(nodeName, discriminator, definitions, schemaNode, oaversion);
427         }
428         return null;
429     }
430
431     private static ObjectNode createPostJsonSchema(final SchemaNode dataNode, final ObjectNode properties,
432             final String postNodeName, final String description) {
433         final ObjectNode postSchema = JsonNodeFactory.instance.objectNode();
434         final ObjectNode postItemProperties;
435         if (dataNode instanceof ListSchemaNode) {
436             postItemProperties = createListItemProperties(properties, (ListSchemaNode) dataNode);
437         } else {
438             postItemProperties = properties.deepCopy();
439         }
440         postSchema.put(TYPE_KEY, OBJECT_TYPE);
441         postSchema.set(PROPERTIES_KEY, postItemProperties);
442         postSchema.put(TITLE_KEY, postNodeName);
443         postSchema.put(DESCRIPTION_KEY, description);
444         postSchema.set(XML_KEY, buildXmlParameter(dataNode));
445         return postSchema;
446     }
447
448     private static ObjectNode createListItemProperties(final ObjectNode properties, final ListSchemaNode listNode) {
449         final ObjectNode postListItemProperties = JsonNodeFactory.instance.objectNode();
450         final List<QName> keyDefinition = listNode.getKeyDefinition();
451         final Set<String> keys = listNode.getChildNodes().stream()
452                 .filter(node -> keyDefinition.contains(node.getQName()))
453                 .map(node -> node.getQName().getLocalName())
454                 .collect(Collectors.toSet());
455
456         Iterator<Map.Entry<String, JsonNode>> it = properties.fields();
457         while (it.hasNext()) {
458             Map.Entry<String, JsonNode> property = it.next();
459             if (!keys.contains(property.getKey())) {
460                 postListItemProperties.set(property.getKey(), property.getValue());
461             }
462         }
463
464         return postListItemProperties;
465     }
466
467     /**
468      * Processes the nodes.
469      */
470     private ObjectNode processChildren(final ObjectNode parentNode, final Collection<? extends DataSchemaNode> nodes,
471             final String parentName, final ObjectNode definitions, final DefinitionNames definitionNames,
472             final boolean isConfig, final SchemaInferenceStack stack, final OAversion oaversion) throws IOException {
473         final ObjectNode properties = JsonNodeFactory.instance.objectNode();
474         final ArrayNode required = JsonNodeFactory.instance.arrayNode();
475         for (final DataSchemaNode node : nodes) {
476             if (!isConfig || node.isConfiguration()) {
477                 processChildNode(node, parentName, definitions, definitionNames, isConfig, stack, properties,
478                         oaversion);
479             }
480         }
481         parentNode.set(PROPERTIES_KEY, properties);
482         setRequiredIfNotEmpty(parentNode, required);
483         return properties;
484     }
485
486     private void processChildNode(final DataSchemaNode node, final String parentName, final ObjectNode definitions,
487             final DefinitionNames definitionNames, final boolean isConfig, final SchemaInferenceStack stack,
488             final ObjectNode properties, final OAversion oaversion) throws IOException {
489
490         stack.enterSchemaTree(node.getQName());
491
492         /*
493             Add module name prefix to property name, when needed, when ServiceNow can process colons,
494             use RestDocGenUtil#resolveNodesName for creating property name
495          */
496         final String name = node.getQName().getLocalName();
497
498         if (node instanceof LeafSchemaNode leaf) {
499             processLeafNode(leaf, name, properties, JsonNodeFactory.instance.arrayNode(), stack, definitions,
500                     definitionNames, oaversion);
501
502         } else if (node instanceof AnyxmlSchemaNode anyxml) {
503             processAnyXMLNode(anyxml, name, properties, JsonNodeFactory.instance.arrayNode());
504
505         } else if (node instanceof AnydataSchemaNode anydata) {
506             processAnydataNode(anydata, name, properties, JsonNodeFactory.instance.arrayNode());
507
508         } else {
509
510             final ObjectNode property;
511             if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
512                 property = processDataNodeContainer((DataNodeContainer) node, parentName, definitions,
513                         definitionNames, isConfig, stack, oaversion);
514                 if (!isConfig) {
515                     processActionNodeContainer(node, parentName, definitions, definitionNames, stack, oaversion);
516                 }
517             } else if (node instanceof LeafListSchemaNode leafList) {
518                 property = processLeafListNode(leafList, stack, definitions, definitionNames, oaversion);
519
520             } else if (node instanceof ChoiceSchemaNode choice) {
521                 for (final CaseSchemaNode variant : choice.getCases()) {
522                     stack.enterSchemaTree(variant.getQName());
523                     for (final DataSchemaNode childNode : variant.getChildNodes()) {
524                         processChildNode(childNode, parentName, definitions, definitionNames, isConfig, stack,
525                                 properties, oaversion);
526                     }
527                     stack.exit();
528                 }
529                 property = null;
530
531             } else {
532                 throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
533             }
534             if (property != null) {
535                 properties.set(name, property);
536             }
537         }
538
539         stack.exit();
540     }
541
542     private ObjectNode processLeafListNode(final LeafListSchemaNode listNode, final SchemaInferenceStack stack,
543             final ObjectNode definitions, final DefinitionNames definitionNames, final OAversion oaversion) {
544         final ObjectNode props = JsonNodeFactory.instance.objectNode();
545         props.put(TYPE_KEY, ARRAY_TYPE);
546
547         final ObjectNode itemsVal = JsonNodeFactory.instance.objectNode();
548         final Optional<ElementCountConstraint> optConstraint = listNode.getElementCountConstraint();
549         processElementCount(optConstraint, props);
550
551         processTypeDef(listNode.getType(), listNode, itemsVal, stack, definitions, definitionNames, oaversion);
552         props.set(ITEMS_KEY, itemsVal);
553
554         props.put(DESCRIPTION_KEY, listNode.getDescription().orElse(""));
555
556         return props;
557     }
558
559     private static void processElementCount(final Optional<ElementCountConstraint> constraint, final ObjectNode props) {
560         if (constraint.isPresent()) {
561             final ElementCountConstraint constr = constraint.get();
562             final Integer minElements = constr.getMinElements();
563             if (minElements != null) {
564                 props.put(MIN_ITEMS, minElements);
565             }
566             final Integer maxElements = constr.getMaxElements();
567             if (maxElements != null) {
568                 props.put(MAX_ITEMS, maxElements);
569             }
570         }
571     }
572
573     private static void processMandatory(final MandatoryAware node, final String nodeName, final ArrayNode required) {
574         if (node.isMandatory()) {
575             required.add(nodeName);
576         }
577     }
578
579     private ObjectNode processLeafNode(final LeafSchemaNode leafNode, final String jsonLeafName,
580             final ObjectNode properties, final ArrayNode required, final SchemaInferenceStack stack,
581             final ObjectNode definitions, final DefinitionNames definitionNames, final OAversion oaversion) {
582         final ObjectNode property = JsonNodeFactory.instance.objectNode();
583
584         final String leafDescription = leafNode.getDescription().orElse("");
585         /*
586             Description can't be added, because nothing allowed alongside $ref.
587             allOf is not an option, because ServiceNow can't parse it.
588         */
589         if (!(leafNode.getType() instanceof IdentityrefTypeDefinition)) {
590             property.put(DESCRIPTION_KEY, leafDescription);
591         }
592
593         processTypeDef(leafNode.getType(), leafNode, property, stack, definitions, definitionNames, oaversion);
594         properties.set(jsonLeafName, property);
595         property.set(XML_KEY, buildXmlParameter(leafNode));
596         processMandatory(leafNode, jsonLeafName, required);
597
598         return property;
599     }
600
601     private static ObjectNode processAnydataNode(final AnydataSchemaNode leafNode, final String name,
602             final ObjectNode properties, final ArrayNode required) {
603         final ObjectNode property = JsonNodeFactory.instance.objectNode();
604
605         final String leafDescription = leafNode.getDescription().orElse("");
606         property.put(DESCRIPTION_KEY, leafDescription);
607
608         final String localName = leafNode.getQName().getLocalName();
609         setDefaultValue(property, String.format("<%s> ... </%s>", localName, localName));
610         property.put(TYPE_KEY, STRING_TYPE);
611         property.set(XML_KEY, buildXmlParameter(leafNode));
612         processMandatory(leafNode, name, required);
613         properties.set(name, property);
614
615         return property;
616     }
617
618     private static ObjectNode processAnyXMLNode(final AnyxmlSchemaNode leafNode, final String name,
619             final ObjectNode properties, final ArrayNode required) {
620         final ObjectNode property = JsonNodeFactory.instance.objectNode();
621
622         final String leafDescription = leafNode.getDescription().orElse("");
623         property.put(DESCRIPTION_KEY, leafDescription);
624
625         final String localName = leafNode.getQName().getLocalName();
626         setDefaultValue(property, String.format("<%s> ... </%s>", localName, localName));
627         property.put(TYPE_KEY, STRING_TYPE);
628         property.set(XML_KEY, buildXmlParameter(leafNode));
629         processMandatory(leafNode, name, required);
630         properties.set(name, property);
631
632         return property;
633     }
634
635     private String processTypeDef(final TypeDefinition<?> leafTypeDef, final DataSchemaNode node,
636             final ObjectNode property, final SchemaInferenceStack stack, final ObjectNode definitions,
637             final DefinitionNames definitionNames, final OAversion oaversion) {
638         final String jsonType;
639         if (leafTypeDef instanceof BinaryTypeDefinition) {
640             jsonType = processBinaryType(property);
641
642         } else if (leafTypeDef instanceof BitsTypeDefinition) {
643             jsonType = processBitsType((BitsTypeDefinition) leafTypeDef, property);
644
645         } else if (leafTypeDef instanceof EnumTypeDefinition) {
646             jsonType = processEnumType((EnumTypeDefinition) leafTypeDef, property);
647
648         } else if (leafTypeDef instanceof IdentityrefTypeDefinition) {
649             jsonType = processIdentityRefType((IdentityrefTypeDefinition) leafTypeDef, property, definitions,
650                     definitionNames, oaversion, stack.getEffectiveModelContext());
651
652         } else if (leafTypeDef instanceof StringTypeDefinition) {
653             jsonType = processStringType(leafTypeDef, property, node.getQName().getLocalName());
654
655         } else if (leafTypeDef instanceof UnionTypeDefinition) {
656             jsonType = processUnionType((UnionTypeDefinition) leafTypeDef);
657
658         } else if (leafTypeDef instanceof EmptyTypeDefinition) {
659             jsonType = OBJECT_TYPE;
660         } else if (leafTypeDef instanceof LeafrefTypeDefinition) {
661             return processTypeDef(stack.resolveLeafref((LeafrefTypeDefinition) leafTypeDef), node, property,
662                 stack, definitions, definitionNames, oaversion);
663         } else if (leafTypeDef instanceof BooleanTypeDefinition) {
664             jsonType = BOOLEAN_TYPE;
665             setDefaultValue(property, true);
666         } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
667             jsonType = processNumberType((RangeRestrictedTypeDefinition<?, ?>) leafTypeDef, property);
668         } else if (leafTypeDef instanceof InstanceIdentifierTypeDefinition) {
669             jsonType = processInstanceIdentifierType(node, property, stack.getEffectiveModelContext());
670         } else {
671             jsonType = STRING_TYPE;
672         }
673         if (!(leafTypeDef instanceof IdentityrefTypeDefinition)) {
674             putIfNonNull(property, TYPE_KEY, jsonType);
675             if (leafTypeDef.getDefaultValue().isPresent()) {
676                 final Object defaultValue = leafTypeDef.getDefaultValue().get();
677                 if (defaultValue instanceof String stringDefaultValue) {
678                     if (leafTypeDef instanceof BooleanTypeDefinition) {
679                         setDefaultValue(property, Boolean.valueOf(stringDefaultValue));
680                     } else if (leafTypeDef instanceof DecimalTypeDefinition
681                             || leafTypeDef instanceof Uint64TypeDefinition) {
682                         setDefaultValue(property, new BigDecimal(stringDefaultValue));
683                     } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
684                         //uint8,16,32 int8,16,32,64
685                         if (isHexadecimalOrOctal((RangeRestrictedTypeDefinition<?, ?>)leafTypeDef)) {
686                             setDefaultValue(property, stringDefaultValue);
687                         } else {
688                             setDefaultValue(property, Long.valueOf(stringDefaultValue));
689                         }
690                     } else {
691                         setDefaultValue(property, stringDefaultValue);
692                     }
693                 } else {
694                     //we should never get here. getDefaultValue always gives us string
695                     setDefaultValue(property, defaultValue.toString());
696                 }
697             }
698         }
699         return jsonType;
700     }
701
702     private static String processBinaryType(final ObjectNode property) {
703         property.put(FORMAT_KEY, "byte");
704         return STRING_TYPE;
705     }
706
707     private static String processEnumType(final EnumTypeDefinition enumLeafType,
708             final ObjectNode property) {
709         final List<EnumPair> enumPairs = enumLeafType.getValues();
710         final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
711         for (final EnumPair enumPair : enumPairs) {
712             enumNames.add(new TextNode(enumPair.getName()));
713         }
714
715         property.set(ENUM_KEY, enumNames);
716         setDefaultValue(property, enumLeafType.getValues().iterator().next().getName());
717         return STRING_TYPE;
718     }
719
720     private String processIdentityRefType(final IdentityrefTypeDefinition leafTypeDef, final ObjectNode property,
721             final ObjectNode definitions, final DefinitionNames definitionNames, final OAversion oaversion,
722             final EffectiveModelContext schemaContext) {
723         final String definitionName;
724         if (isImported(leafTypeDef)) {
725             definitionName = addImportedIdentity(leafTypeDef, definitions, definitionNames, schemaContext);
726         } else {
727             final SchemaNode node = leafTypeDef.getIdentities().iterator().next();
728             definitionName = node.getQName().getLocalName() + definitionNames.getDiscriminator(node);
729         }
730         property.put(REF_KEY, getAppropriateModelPrefix(oaversion) + definitionName);
731         return STRING_TYPE;
732     }
733
734     private static String addImportedIdentity(final IdentityrefTypeDefinition leafTypeDef,
735             final ObjectNode definitions, final DefinitionNames definitionNames, final EffectiveModelContext context) {
736         final IdentitySchemaNode idNode = leafTypeDef.getIdentities().iterator().next();
737         final String identityName = idNode.getQName().getLocalName();
738         if (!definitionNames.isListedNode(idNode)) {
739             final ObjectNode identityObj = buildIdentityObject(idNode, context);
740             final String discriminator = definitionNames.pickDiscriminator(idNode, List.of(identityName));
741             final String name = identityName + discriminator;
742             definitions.set(name, identityObj);
743             return name;
744         } else {
745             return identityName + definitionNames.getDiscriminator(idNode);
746         }
747     }
748
749     private static ObjectNode buildIdentityObject(final IdentitySchemaNode idNode,
750             final EffectiveModelContext context) {
751         final ObjectNode identityObj = JsonNodeFactory.instance.objectNode();
752         final String identityName = idNode.getQName().getLocalName();
753         LOG.debug("Processing Identity: {}", identityName);
754
755         identityObj.put(TITLE_KEY, identityName);
756         identityObj.put(DESCRIPTION_KEY, idNode.getDescription().orElse(""));
757
758         final Collection<? extends IdentitySchemaNode> derivedIds = context.getDerivedIdentities(idNode);
759
760         final ArrayNode enumPayload = JsonNodeFactory.instance.arrayNode();
761         enumPayload.add(identityName);
762         populateEnumWithDerived(derivedIds, enumPayload, context);
763         identityObj.set(ENUM_KEY, enumPayload);
764         identityObj.put(TYPE_KEY, STRING_TYPE);
765         return identityObj;
766     }
767
768     private boolean isImported(final IdentityrefTypeDefinition leafTypeDef) {
769         return !leafTypeDef.getQName().getModule().equals(topLevelModule.getQNameModule());
770     }
771
772     private static String processBitsType(final BitsTypeDefinition bitsType, final ObjectNode property) {
773         property.put(MIN_ITEMS, 0);
774         property.put(UNIQUE_ITEMS_KEY, true);
775         final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
776         final Collection<? extends Bit> bits = bitsType.getBits();
777         for (final Bit bit : bits) {
778             enumNames.add(new TextNode(bit.getName()));
779         }
780         property.set(ENUM_KEY, enumNames);
781         property.put(DEFAULT_KEY, enumNames.iterator().next() + " " + enumNames.get(enumNames.size() - 1));
782         return STRING_TYPE;
783     }
784
785     private static String processStringType(final TypeDefinition<?> stringType, final ObjectNode property,
786             final String nodeName) {
787         StringTypeDefinition type = (StringTypeDefinition) stringType;
788         Optional<LengthConstraint> lengthConstraints = ((StringTypeDefinition) stringType).getLengthConstraint();
789         while (lengthConstraints.isEmpty() && type.getBaseType() != null) {
790             type = type.getBaseType();
791             lengthConstraints = type.getLengthConstraint();
792         }
793
794         if (lengthConstraints.isPresent()) {
795             final Range<Integer> range = lengthConstraints.get().getAllowedRanges().span();
796             putIfNonNull(property, MIN_LENGTH_KEY, range.lowerEndpoint());
797             putIfNonNull(property, MAX_LENGTH_KEY, range.upperEndpoint());
798         }
799
800         if (type.getPatternConstraints().iterator().hasNext()) {
801             final PatternConstraint pattern = type.getPatternConstraints().iterator().next();
802             String regex = pattern.getJavaPatternString();
803             regex = regex.substring(1, regex.length() - 1);
804             // Escape special characters to prevent issues inside Generex.
805             regex = AUTOMATON_SPECIAL_CHARACTERS.matcher(regex).replaceAll("\\\\$0");
806             String defaultValue = "";
807             try {
808                 final Generex generex = new Generex(regex);
809                 defaultValue = generex.random();
810             } catch (IllegalArgumentException ex) {
811                 LOG.warn("Cannot create example string for type: {} with regex: {}.", stringType.getQName(), regex);
812             }
813             setDefaultValue(property, defaultValue);
814         } else {
815             setDefaultValue(property, "Some " + nodeName);
816         }
817         return STRING_TYPE;
818     }
819
820     private static String processNumberType(final RangeRestrictedTypeDefinition<?, ?> leafTypeDef,
821             final ObjectNode property) {
822         final Optional<Number> maybeLower = leafTypeDef.getRangeConstraint()
823                 .map(RangeConstraint::getAllowedRanges).map(RangeSet::span).map(Range::lowerEndpoint);
824
825         if (isHexadecimalOrOctal(leafTypeDef)) {
826             return STRING_TYPE;
827         }
828
829         if (leafTypeDef instanceof DecimalTypeDefinition) {
830             maybeLower.ifPresent(number -> setDefaultValue(property, ((Decimal64) number).decimalValue()));
831             return NUMBER_TYPE;
832         }
833         if (leafTypeDef instanceof Uint8TypeDefinition
834                 || leafTypeDef instanceof Uint16TypeDefinition
835                 || leafTypeDef instanceof Int8TypeDefinition
836                 || leafTypeDef instanceof Int16TypeDefinition
837                 || leafTypeDef instanceof Int32TypeDefinition) {
838
839             property.put(FORMAT_KEY, INT32_FORMAT);
840             maybeLower.ifPresent(number -> setDefaultValue(property, Integer.valueOf(number.toString())));
841         } else if (leafTypeDef instanceof Uint32TypeDefinition
842                 || leafTypeDef instanceof Int64TypeDefinition) {
843
844             property.put(FORMAT_KEY, INT64_FORMAT);
845             maybeLower.ifPresent(number -> setDefaultValue(property, Long.valueOf(number.toString())));
846         } else {
847             //uint64
848             setDefaultValue(property, 0);
849         }
850         return INTEGER_TYPE;
851     }
852
853     private static boolean isHexadecimalOrOctal(final RangeRestrictedTypeDefinition<?, ?> typeDef) {
854         final Optional<?> optDefaultValue = typeDef.getDefaultValue();
855         if (optDefaultValue.isPresent()) {
856             final String defaultValue = (String) optDefaultValue.get();
857             return defaultValue.startsWith("0") || defaultValue.startsWith("-0");
858         }
859         return false;
860     }
861
862     private static String processInstanceIdentifierType(final DataSchemaNode node, final ObjectNode property,
863             final EffectiveModelContext schemaContext) {
864         // create example instance-identifier to the first container of node's module if exists or leave it empty
865         final var module = schemaContext.findModule(node.getQName().getModule());
866         if (module.isPresent()) {
867             final var container = module.get().getChildNodes().stream()
868                     .filter(n -> n instanceof ContainerSchemaNode)
869                     .findFirst();
870             container.ifPresent(c -> setDefaultValue(property, String.format("/%s:%s", module.get().getPrefix(),
871                     c.getQName().getLocalName())));
872         }
873
874         return STRING_TYPE;
875     }
876
877     private static String processUnionType(final UnionTypeDefinition unionType) {
878         boolean isStringTakePlace = false;
879         boolean isNumberTakePlace = false;
880         boolean isBooleanTakePlace = false;
881         for (final TypeDefinition<?> typeDef : unionType.getTypes()) {
882             if (!isStringTakePlace) {
883                 if (typeDef instanceof StringTypeDefinition
884                         || typeDef instanceof BitsTypeDefinition
885                         || typeDef instanceof BinaryTypeDefinition
886                         || typeDef instanceof IdentityrefTypeDefinition
887                         || typeDef instanceof EnumTypeDefinition
888                         || typeDef instanceof LeafrefTypeDefinition
889                         || typeDef instanceof UnionTypeDefinition) {
890                     isStringTakePlace = true;
891                 } else if (!isNumberTakePlace && typeDef instanceof RangeRestrictedTypeDefinition) {
892                     isNumberTakePlace = true;
893                 } else if (!isBooleanTakePlace && typeDef instanceof BooleanTypeDefinition) {
894                     isBooleanTakePlace = true;
895                 }
896             }
897         }
898         if (isStringTakePlace) {
899             return STRING_TYPE;
900         }
901         if (isBooleanTakePlace) {
902             if (isNumberTakePlace) {
903                 return STRING_TYPE;
904             }
905             return BOOLEAN_TYPE;
906         }
907         return NUMBER_TYPE;
908     }
909
910     private static ObjectNode buildXmlParameter(final SchemaNode node) {
911         final ObjectNode xml = JsonNodeFactory.instance.objectNode();
912         final QName qName = node.getQName();
913         xml.put(NAME_KEY, qName.getLocalName());
914         xml.put(NAMESPACE_KEY, qName.getNamespace().toString());
915         return xml;
916     }
917
918     private static void putIfNonNull(final ObjectNode property, final String key, final Number number) {
919         if (key != null && number != null) {
920             if (number instanceof Double) {
921                 property.put(key, (Double) number);
922             } else if (number instanceof Float) {
923                 property.put(key, (Float) number);
924             } else if (number instanceof Integer) {
925                 property.put(key, (Integer) number);
926             } else if (number instanceof Short) {
927                 property.put(key, (Short) number);
928             } else if (number instanceof Long) {
929                 property.put(key, (Long) number);
930             }
931         }
932     }
933
934     private static void putIfNonNull(final ObjectNode property, final String key, final String value) {
935         if (key != null && value != null) {
936             property.put(key, value);
937         }
938     }
939
940     private static void setRequiredIfNotEmpty(final ObjectNode node, final ArrayNode required) {
941         if (required.size() > 0) {
942             node.set(REQUIRED_KEY, required);
943         }
944     }
945
946     private static void setDefaultValue(final ObjectNode property, final String value) {
947         property.put(DEFAULT_KEY, value);
948     }
949
950     private static void setDefaultValue(final ObjectNode property, final Integer value) {
951         property.put(DEFAULT_KEY, value);
952     }
953
954     private static void setDefaultValue(final ObjectNode property, final Long value) {
955         property.put(DEFAULT_KEY, value);
956     }
957
958     private static void setDefaultValue(final ObjectNode property, final BigDecimal value) {
959         property.put(DEFAULT_KEY, value);
960     }
961
962     private static void setDefaultValue(final ObjectNode property, final Boolean value) {
963         property.put(DEFAULT_KEY, value);
964     }
965
966 }