2 * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.restconf.openapi.model;
10 import static java.util.Objects.requireNonNull;
12 import com.fasterxml.jackson.core.JsonGenerator;
13 import com.google.common.collect.Range;
14 import com.google.common.collect.RangeSet;
15 import dk.brics.automaton.RegExp;
16 import java.io.IOException;
17 import java.math.BigDecimal;
18 import java.math.BigInteger;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.HashMap;
22 import java.util.List;
24 import java.util.regex.Pattern;
25 import java.util.stream.Collectors;
26 import org.eclipse.jdt.annotation.NonNull;
27 import org.opendaylight.restconf.openapi.impl.DefinitionNames;
28 import org.opendaylight.yangtools.yang.common.AbstractQName;
29 import org.opendaylight.yangtools.yang.common.Decimal64;
30 import org.opendaylight.yangtools.yang.common.XMLNamespace;
31 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
36 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
38 import org.opendaylight.yangtools.yang.model.api.ElementCountConstraint;
39 import org.opendaylight.yangtools.yang.model.api.ElementCountConstraintAware;
40 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.MandatoryAware;
45 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
47 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.meta.ModelStatement;
49 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
50 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
51 import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
52 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
53 import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition;
54 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
55 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
56 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
57 import org.opendaylight.yangtools.yang.model.api.type.Int16TypeDefinition;
58 import org.opendaylight.yangtools.yang.model.api.type.Int32TypeDefinition;
59 import org.opendaylight.yangtools.yang.model.api.type.Int64TypeDefinition;
60 import org.opendaylight.yangtools.yang.model.api.type.Int8TypeDefinition;
61 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
62 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
63 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
64 import org.opendaylight.yangtools.yang.model.api.type.RangeRestrictedTypeDefinition;
65 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
66 import org.opendaylight.yangtools.yang.model.api.type.Uint16TypeDefinition;
67 import org.opendaylight.yangtools.yang.model.api.type.Uint32TypeDefinition;
68 import org.opendaylight.yangtools.yang.model.api.type.Uint64TypeDefinition;
69 import org.opendaylight.yangtools.yang.model.api.type.Uint8TypeDefinition;
70 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
71 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
75 public class PropertyEntity {
76 private static final Logger LOG = LoggerFactory.getLogger(PropertyEntity.class);
77 private static final String COMPONENTS_PREFIX = "#/components/schemas/";
78 private static final String ARRAY_TYPE = "array";
79 private static final String DESCRIPTION = "description";
80 private static final String DEFAULT = "default";
81 private static final String ENUM = "enum";
82 private static final String EXAMPLE = "example";
83 private static final String FORMAT = "format";
84 private static final String ITEMS = "items";
85 private static final String STRING_TYPE = "string";
86 private static final String OBJECT_TYPE = "object";
87 private static final String NUMBER_TYPE = "number";
88 private static final String TYPE = "type";
89 private static final Pattern AUTOMATON_SPECIAL_CHARACTERS = Pattern.compile("[@&\"<>#~]");
90 // Adaptation from YANG regex to Automaton regex
91 // See https://github.com/mifmif/Generex/blob/master/src/main/java/com/mifmif/common/regex/Generex.java
92 private static final Map<String, String> PREDEFINED_CHARACTER_CLASSES = Map.of("\\\\d", "[0-9]",
93 "\\\\D", "[^0-9]", "\\\\s", "[ \t\n\f\r]", "\\\\S", "[^ \t\n\f\r]",
94 "\\\\w", "[a-zA-Z_0-9]", "\\\\W", "[^a-zA-Z_0-9]");
96 private final @NonNull DataSchemaNode node;
97 private final @NonNull JsonGenerator generator;
98 private final @NonNull List<String> required;
99 private final @NonNull String parentName;
100 private final @NonNull DefinitionNames definitionNames;
102 public PropertyEntity(final @NonNull DataSchemaNode node, final @NonNull JsonGenerator generator,
103 final @NonNull SchemaInferenceStack stack, final @NonNull List<String> required,
104 final @NonNull String parentName, final boolean isParentConfig,
105 final @NonNull DefinitionNames definitionNames) throws IOException {
106 this.node = requireNonNull(node);
107 this.generator = requireNonNull(generator);
108 this.required = requireNonNull(required);
109 this.parentName = requireNonNull(parentName);
110 this.definitionNames = requireNonNull(definitionNames);
111 generate(stack, isParentConfig);
114 private void generate(final SchemaInferenceStack stack, final boolean isParentConfig) throws IOException {
115 if (node instanceof ChoiceSchemaNode choice) {
116 stack.enterSchemaTree(node.getQName());
117 final var isConfig = isParentConfig && node.isConfiguration();
118 processChoiceNodeRecursively(isConfig, stack, choice);
121 generator.writeObjectFieldStart(node.getQName().getLocalName());
122 processChildNode(node, stack, isParentConfig);
123 generator.writeEndObject();
127 private void processChoiceNodeRecursively(final boolean isConfig, final SchemaInferenceStack stack,
128 final ChoiceSchemaNode choice) throws IOException {
129 if (!choice.getCases().isEmpty()) {
130 final var caseSchemaNode = choice.getDefaultCase().orElse(
131 choice.getCases().stream().findFirst().orElseThrow());
132 stack.enterSchemaTree(caseSchemaNode.getQName());
133 for (final var childNode : caseSchemaNode.getChildNodes()) {
134 if (childNode instanceof ChoiceSchemaNode childChoice) {
135 final var isChildConfig = isConfig && childNode.isConfiguration();
136 stack.enterSchemaTree(childNode.getQName());
137 processChoiceNodeRecursively(isChildConfig, stack, childChoice);
139 } else if (!isConfig || childNode.isConfiguration()) {
140 generator.writeObjectFieldStart(childNode.getQName().getLocalName());
141 processChildNode(childNode, stack, isConfig);
142 generator.writeEndObject();
149 private void processChildNode(final DataSchemaNode schemaNode, final SchemaInferenceStack stack,
150 final boolean isParentConfig) throws IOException {
151 final var parentNamespace = stack.toSchemaNodeIdentifier().lastNodeIdentifier().getNamespace();
152 stack.enterSchemaTree(schemaNode.getQName());
153 final var name = schemaNode.getQName().getLocalName();
154 final var shouldBeAddedAsChild = !isParentConfig || schemaNode.isConfiguration();
155 if (schemaNode instanceof ListSchemaNode || schemaNode instanceof ContainerSchemaNode) {
156 processDataNodeContainer((DataNodeContainer) schemaNode, stack);
157 if (shouldBeAddedAsChild && isSchemaNodeMandatory(schemaNode)) {
160 } else if (shouldBeAddedAsChild) {
161 if (schemaNode instanceof LeafSchemaNode leaf) {
162 processLeafNode(leaf, name, stack, parentNamespace);
163 } else if (schemaNode instanceof AnyxmlSchemaNode || schemaNode instanceof AnydataSchemaNode) {
164 processUnknownDataSchemaNode(schemaNode, name, parentNamespace);
165 } else if (schemaNode instanceof LeafListSchemaNode leafList) {
166 if (isSchemaNodeMandatory(schemaNode)) {
169 processLeafListNode(leafList, stack);
171 throw new IllegalArgumentException("Unknown DataSchemaNode type: " + schemaNode.getClass());
177 private void processDataNodeContainer(final DataNodeContainer dataNode, final SchemaInferenceStack stack)
179 final var schemaNode = (SchemaNode) dataNode;
180 final var localName = schemaNode.getQName().getLocalName();
181 final var nodeName = parentName + "_" + localName;
183 final String discriminator;
184 if (!definitionNames.isListedNode(schemaNode)) {
185 final var parentNameConfigLocalName = parentName + "_" + localName;
186 final var names = List.of(parentNameConfigLocalName);
187 discriminator = definitionNames.pickDiscriminator(schemaNode, names);
189 discriminator = definitionNames.getDiscriminator(schemaNode);
192 processRef(nodeName, schemaNode, discriminator, stack);
195 private void processRef(final String name, final SchemaNode schemaNode, String discriminator,
196 final SchemaInferenceStack stack) throws IOException {
197 final var ref = COMPONENTS_PREFIX + name + discriminator;
198 if (schemaNode instanceof ListSchemaNode listNode) {
199 generator.writeStringField(TYPE, ARRAY_TYPE);
200 generator.writeObjectFieldStart(ITEMS);
201 generator.writeStringField("$ref", ref);
202 generator.writeEndObject();
203 generator.writeStringField(DESCRIPTION, schemaNode.getDescription().orElse(""));
205 if (listNode.getElementCountConstraint().isPresent()) {
206 final var minElements = listNode.getElementCountConstraint().orElseThrow().getMinElements();
207 final var maxElements = listNode.getElementCountConstraint().orElseThrow().getMaxElements();
208 if (minElements != null) {
209 createExamples(listNode, minElements, stack);
210 generator.writeNumberField("minItems", minElements);
212 if (maxElements != null) {
213 generator.writeNumberField("maxItems", maxElements);
218 Description can't be added, because nothing allowed alongside $ref.
219 allOf is not an option, because ServiceNow can't parse it.
221 generator.writeStringField("$ref", ref);
225 private void createExamples(final ListSchemaNode schemaNode,
226 @NonNull final Integer minElements, final SchemaInferenceStack stack) throws IOException {
227 final var firstExampleMap = prepareFirstListExample(schemaNode, stack);
228 final var examples = new ArrayList<Map<String, Object>>();
229 examples.add(firstExampleMap);
231 final var unqiueContraintsNameSet = schemaNode.getUniqueConstraints().stream()
232 .map(ModelStatement::argument)
233 .flatMap(uniqueSt -> uniqueSt.stream()
234 .map(schemaNI -> schemaNI.lastNodeIdentifier().getLocalName()))
235 .collect(Collectors.toSet());
236 final var keysNameSet = schemaNode.getKeyDefinition().stream()
237 .map(AbstractQName::getLocalName)
238 .collect(Collectors.toSet());
239 for (int i = 1; i < minElements; i++) {
240 final var exampleMap = new HashMap<String, Object>();
241 for (final var example : firstExampleMap.entrySet()) {
242 final Object exampleValue;
243 if (keysNameSet.contains(example.getKey()) || unqiueContraintsNameSet.contains(example.getKey())) {
244 exampleValue = editExample(example.getValue(), i);
246 exampleValue = example.getValue();
248 exampleMap.put(example.getKey(), exampleValue);
250 examples.add(exampleMap);
252 generator.writeArrayFieldStart(EXAMPLE);
253 for (final var example : examples) {
254 generator.writeStartObject();
255 for (final var elem : example.entrySet()) {
256 writeValue(elem.getKey(), elem.getValue());
258 generator.writeEndObject();
260 generator.writeEndArray();
263 private HashMap<String, Object> prepareFirstListExample(final ListSchemaNode schemaNode,
264 final SchemaInferenceStack stack) {
265 final var childNodes = schemaNode.getChildNodes();
266 final var firstExampleMap = new HashMap<String, Object>();
267 // Cycle for each child node
268 for (final var childNode : childNodes) {
269 if (childNode instanceof TypedDataSchemaNode leafSchemaNode) {
270 final var def = new TypeDef();
271 processTypeDef(leafSchemaNode.getType(), leafSchemaNode, stack, def);
272 if (def.hasExample()) {
273 firstExampleMap.put(leafSchemaNode.getQName().getLocalName(), def.getExample());
277 return firstExampleMap;
280 private Object editExample(final Object exampleValue, final int edit) {
281 if (exampleValue instanceof String string) {
282 return string + "_" + edit;
283 } else if (exampleValue instanceof Integer number) {
284 return number + edit;
285 } else if (exampleValue instanceof Long number) {
286 return number + edit;
287 } else if (exampleValue instanceof Decimal64 number) {
288 return Decimal64.valueOf(BigDecimal.valueOf(number.intValue() + edit));
293 private void processUnknownDataSchemaNode(final DataSchemaNode leafNode, final String name,
294 final XMLNamespace parentNamespace) throws IOException {
295 assert (leafNode instanceof AnydataSchemaNode || leafNode instanceof AnyxmlSchemaNode);
297 final var leafDescription = leafNode.getDescription().orElse("");
298 generator.writeStringField(DESCRIPTION, leafDescription);
300 final var localName = leafNode.getQName().getLocalName();
301 generator.writeStringField(EXAMPLE, String.format("<%s> ... </%s>", localName, localName));
302 generator.writeStringField(TYPE, STRING_TYPE);
303 if (!leafNode.getQName().getNamespace().equals(parentNamespace)) {
304 // If the parent is not from the same model, define the child XML namespace.
305 buildXmlParameter(leafNode);
307 processMandatory((MandatoryAware) leafNode, name, required);
310 private void processLeafListNode(final LeafListSchemaNode listNode, final SchemaInferenceStack stack)
312 generator.writeStringField(TYPE, ARRAY_TYPE);
314 Integer minElements = null;
315 final var optConstraint = listNode.getElementCountConstraint();
316 if (optConstraint.isPresent()) {
317 minElements = optConstraint.orElseThrow().getMinElements();
318 if (minElements != null) {
319 generator.writeNumberField("minItems", minElements);
321 final var maxElements = optConstraint.orElseThrow().getMaxElements();
322 if (maxElements != null) {
323 generator.writeNumberField("maxItems", maxElements);
326 final var def = new TypeDef();
327 processTypeDef(listNode.getType(), listNode, stack, def);
329 generator.writeObjectFieldStart(ITEMS);
330 processTypeDef(listNode.getType(), listNode, stack);
331 generator.writeEndObject();
332 generator.writeStringField(DESCRIPTION, listNode.getDescription().orElse(""));
334 if (def.hasExample() && minElements != null) {
335 final var listOfExamples = new ArrayList<>();
336 for (int i = 0; i < minElements; i++) {
337 listOfExamples.add(def.getExample());
339 generator.writeArrayFieldStart(EXAMPLE);
340 for (final var example : listOfExamples) {
341 generator.writeString(example.toString());
343 generator.writeEndArray();
347 private void processLeafNode(final LeafSchemaNode leafNode, final String jsonLeafName,
348 final SchemaInferenceStack stack, final XMLNamespace parentNamespace) throws IOException {
349 final var leafDescription = leafNode.getDescription().orElse("");
350 generator.writeStringField(DESCRIPTION, leafDescription);
351 processTypeDef(leafNode.getType(), leafNode, stack);
352 if (!leafNode.getQName().getNamespace().equals(parentNamespace)) {
353 // If the parent is not from the same model, define the child XML namespace.
354 buildXmlParameter(leafNode);
356 processMandatory(leafNode, jsonLeafName, required);
359 private static void processMandatory(final MandatoryAware node, final String nodeName,
360 final List<String> required) {
361 if (node.isMandatory()) {
362 required.add(nodeName);
366 private void buildXmlParameter(final SchemaNode schemaNode) throws IOException {
367 generator.writeObjectFieldStart("xml");
368 generator.writeStringField("name", schemaNode.getQName().getLocalName());
369 generator.writeStringField("namespace", schemaNode.getQName().getNamespace().toString());
370 generator.writeEndObject();
373 protected static boolean isSchemaNodeMandatory(final DataSchemaNode schemaNode) {
374 if (schemaNode instanceof ContainerSchemaNode containerNode) {
375 if (containerNode.isPresenceContainer()) {
378 for (final var childNode : containerNode.getChildNodes()) {
379 if (childNode instanceof MandatoryAware mandatoryAware && mandatoryAware.isMandatory()) {
384 // A list or leaf-list node with a "min-elements" statement with a value greater than zero.
385 return schemaNode instanceof ElementCountConstraintAware constraintAware
386 && constraintAware.getElementCountConstraint()
387 .map(ElementCountConstraint::getMinElements)
391 private void processTypeDef(final TypeDefinition<?> leafTypeDef, final DataSchemaNode schemaNode,
392 final SchemaInferenceStack stack) throws IOException {
393 final var def = new TypeDef();
395 final var jsonType = processTypeDef(leafTypeDef, schemaNode, stack, def);
397 generator.writeStringField(TYPE, jsonType);
398 generateTypeDef(def);
401 private String processTypeDef(final TypeDefinition<?> leafTypeDef, final DataSchemaNode schemaNode,
402 final SchemaInferenceStack stack, final TypeDef def) {
403 final String jsonType;
404 if (leafTypeDef instanceof BinaryTypeDefinition binaryType) {
405 jsonType = processBinaryType(binaryType, def);
406 } else if (leafTypeDef instanceof BitsTypeDefinition bitsType) {
407 jsonType = processBitsType(bitsType, def);
408 } else if (leafTypeDef instanceof EnumTypeDefinition enumType) {
409 jsonType = processEnumType(enumType, def);
410 } else if (leafTypeDef instanceof IdentityrefTypeDefinition identityrefType) {
411 jsonType = processIdentityRefType(identityrefType, stack.modelContext(), def);
412 } else if (leafTypeDef instanceof StringTypeDefinition stringType) {
413 jsonType = processStringType(stringType, schemaNode.getQName().getLocalName(), def);
414 } else if (leafTypeDef instanceof UnionTypeDefinition unionType) {
415 jsonType = processTypeDef(unionType.getTypes().iterator().next(), schemaNode, stack, def);
416 } else if (leafTypeDef instanceof EmptyTypeDefinition) {
417 jsonType = OBJECT_TYPE;
418 } else if (leafTypeDef instanceof LeafrefTypeDefinition leafrefType) {
419 jsonType = processTypeDef(stack.resolveLeafref(leafrefType), schemaNode, stack, def);
420 } else if (leafTypeDef instanceof BooleanTypeDefinition) {
421 jsonType = "boolean";
422 if (leafTypeDef.getDefaultValue().isPresent()) {
423 def.setDefaultValue(Boolean.parseBoolean((String) leafTypeDef.getDefaultValue().orElseThrow()));
425 def.setExample(true);
426 } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition<?, ?> rangeRestrictedType) {
427 jsonType = processNumberType(rangeRestrictedType, def);
428 } else if (leafTypeDef instanceof InstanceIdentifierTypeDefinition instanceIdentifierType) {
429 jsonType = processInstanceIdentifierType(instanceIdentifierType, schemaNode,
430 stack.modelContext(), def);
432 jsonType = STRING_TYPE;
435 if (leafTypeDef.getDefaultValue().isPresent()) {
436 final var defaultValue = leafTypeDef.getDefaultValue().orElseThrow();
437 if (defaultValue instanceof String stringDefaultValue) {
438 if (leafTypeDef instanceof BooleanTypeDefinition) {
439 def.setDefaultValue(Boolean.valueOf(stringDefaultValue));
440 } else if (leafTypeDef instanceof DecimalTypeDefinition
441 || leafTypeDef instanceof Uint64TypeDefinition) {
442 def.setDefaultValue(new BigDecimal(stringDefaultValue));
443 } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition<?, ?> rangeRestrictedType) {
444 //uint8,16,32 int8,16,32,64
445 if (isHexadecimalOrOctal(rangeRestrictedType)) {
446 def.setDefaultValue(stringDefaultValue);
448 def.setDefaultValue(Long.valueOf(stringDefaultValue));
451 def.setDefaultValue(stringDefaultValue);
458 private void writeValue(final String field, final Object value) throws IOException {
459 if (value instanceof Number) {
460 if (value instanceof Integer intValue) {
461 generator.writeNumberField(field, intValue);
462 } else if (value instanceof Long longValue) {
463 generator.writeNumberField(field, longValue);
464 } else if (value instanceof BigDecimal decimalValue) {
465 generator.writeNumberField(field, decimalValue);
466 } else if (value instanceof BigInteger bigIntValue) {
467 generator.writeNumberField(field, bigIntValue);
469 } else if (value instanceof Boolean bool) {
470 generator.writeBooleanField(field, bool);
472 generator.writeStringField(field, (String) value);
476 private void generateTypeDef(final TypeDef def) throws IOException {
477 if (def.hasEnums()) {
478 generator.writeArrayFieldStart(ENUM);
479 for (final var enumElem : def.getEnums()) {
480 generator.writeString(enumElem);
482 generator.writeEndArray();
484 if (def.hasFormat()) {
485 generator.writeStringField(FORMAT, def.getFormat());
487 if (def.hasMinItems()) {
488 generator.writeNumberField("minItems", def.getMinItems());
490 if (def.hasDefaultValue()) {
491 writeValue(DEFAULT, def.getDefaultValue());
493 if (def.hasExample()) {
494 writeValue(EXAMPLE, def.getExample());
496 if (def.hasUniqueItems()) {
497 generator.writeBooleanField("uniqueItems", def.getUniqueItems());
499 if (def.hasMinLength()) {
500 generator.writeNumberField("minLength", def.getMinLength());
502 if (def.hasMaxLength()) {
503 generator.writeNumberField("maxLength", def.getMaxLength());
507 private String processBinaryType(final BinaryTypeDefinition definition, final TypeDef def) {
508 if (definition.getDefaultValue().isPresent()) {
509 def.setDefaultValue(definition.getDefaultValue().toString());
511 def.setFormat("byte");
515 private String processBitsType(final BitsTypeDefinition bitsType, final TypeDef def) {
517 def.setUniqueItems(true);
518 final var bits = bitsType.getBits();
519 final var enumNames = bits.stream()
520 .map(BitsTypeDefinition.Bit::getName)
523 def.setEnums(enumNames);
525 def.setDefaultValue(bitsType.getDefaultValue().isPresent()
526 ? bitsType.getDefaultValue().orElseThrow().toString() :
527 (enumNames.iterator().next() + " " + enumNames.get(enumNames.size() - 1)));
531 private String processEnumType(final EnumTypeDefinition enumLeafType, final TypeDef def) {
532 final var enumPairs = enumLeafType.getValues();
533 final var enumNames = enumPairs.stream()
534 .map(EnumTypeDefinition.EnumPair::getName)
537 def.setEnums(enumNames);
539 if (enumLeafType.getDefaultValue().isPresent()) {
540 final var defaultValue = enumLeafType.getDefaultValue().orElseThrow().toString();
541 def.setDefaultValue(defaultValue);
543 def.setExample(enumLeafType.getValues().iterator().next().getName());
547 private String processIdentityRefType(final IdentityrefTypeDefinition leafTypeDef,
548 final EffectiveModelContext schemaContext, final TypeDef def) {
549 final var schemaNode = leafTypeDef.getIdentities().iterator().next();
550 def.setExample(schemaNode.getQName().getLocalName());
552 final var derivedIds = schemaContext.getDerivedIdentities(schemaNode);
553 final var enumPayload = new ArrayList<String>();
554 enumPayload.add(schemaNode.getQName().getLocalName());
555 populateEnumWithDerived(derivedIds, enumPayload, schemaContext);
556 final var schemaEnum = new ArrayList<>(enumPayload);
558 def.setEnums(schemaEnum);
563 private void populateEnumWithDerived(final Collection<? extends IdentitySchemaNode> derivedIds,
564 final List<String> enumPayload, final EffectiveModelContext context) {
565 for (final var derivedId : derivedIds) {
566 enumPayload.add(derivedId.getQName().getLocalName());
567 populateEnumWithDerived(context.getDerivedIdentities(derivedId), enumPayload, context);
571 private String processStringType(final StringTypeDefinition stringType, final String nodeName, final TypeDef def) {
572 var type = stringType;
573 while (type.getLengthConstraint().isEmpty() && type.getBaseType() != null) {
574 type = type.getBaseType();
577 if (type.getLengthConstraint().isPresent()) {
578 final var range = type.getLengthConstraint().orElseThrow().getAllowedRanges().span();
579 def.setMinLength(range.lowerEndpoint());
580 def.setMaxLength(range.upperEndpoint());
583 if (type.getPatternConstraints().iterator().hasNext()) {
584 final PatternConstraint pattern = type.getPatternConstraints().iterator().next();
585 var regex = pattern.getRegularExpressionString();
586 // Escape special characters to prevent issues inside Automaton.
587 regex = AUTOMATON_SPECIAL_CHARACTERS.matcher(regex).replaceAll("\\\\$0");
588 for (final var charClass : PREDEFINED_CHARACTER_CLASSES.entrySet()) {
589 regex = regex.replaceAll(charClass.getKey(), charClass.getValue());
591 var defaultValue = "";
593 final var regExp = new RegExp(regex);
594 defaultValue = regExp.toAutomaton().getShortestExample(true);
595 } catch (IllegalArgumentException ex) {
596 LOG.warn("Cannot create example string for type: {} with regex: {}.", stringType.getQName(), regex);
598 def.setExample(defaultValue);
600 def.setExample("Some " + nodeName);
603 if (stringType.getDefaultValue().isPresent()) {
604 def.setDefaultValue(stringType.getDefaultValue().orElseThrow().toString());
609 private String processNumberType(final RangeRestrictedTypeDefinition<?, ?> leafTypeDef,final TypeDef def) {
610 final var maybeLower = leafTypeDef.getRangeConstraint()
611 .map(RangeConstraint::getAllowedRanges).map(RangeSet::span).map(Range::lowerEndpoint);
613 if (isHexadecimalOrOctal(leafTypeDef)) {
617 if (leafTypeDef instanceof DecimalTypeDefinition) {
618 if (leafTypeDef.getDefaultValue().isPresent()) {
619 def.setDefaultValue(Decimal64.valueOf(
620 leafTypeDef.getDefaultValue().orElseThrow().toString()).decimalValue());
622 if (maybeLower.isPresent()) {
623 def.setExample(((Decimal64) maybeLower.orElseThrow()).decimalValue());
627 if (leafTypeDef instanceof Uint8TypeDefinition
628 || leafTypeDef instanceof Uint16TypeDefinition
629 || leafTypeDef instanceof Int8TypeDefinition
630 || leafTypeDef instanceof Int16TypeDefinition
631 || leafTypeDef instanceof Int32TypeDefinition) {
633 def.setFormat("int32");
634 if (leafTypeDef.getDefaultValue().isPresent()) {
635 def.setDefaultValue(Integer.parseInt(leafTypeDef.getDefaultValue().orElseThrow().toString()));
637 if (maybeLower.isPresent()) {
638 def.setExample(Integer.parseInt(maybeLower.orElseThrow().toString()));
640 } else if (leafTypeDef instanceof Uint32TypeDefinition
641 || leafTypeDef instanceof Int64TypeDefinition) {
642 def.setFormat("int64");
643 if (leafTypeDef.getDefaultValue().isPresent()) {
644 def.setDefaultValue(Long.parseLong(leafTypeDef.getDefaultValue().orElseThrow().toString()));
646 if (maybeLower.isPresent()) {
647 def.setExample(Long.parseLong(maybeLower.orElseThrow().toString()));
651 if (leafTypeDef.getDefaultValue().isPresent()) {
652 def.setDefaultValue(new BigInteger(leafTypeDef.getDefaultValue().orElseThrow().toString()));
659 private static boolean isHexadecimalOrOctal(final RangeRestrictedTypeDefinition<?, ?> typeDef) {
660 final var optDefaultValue = typeDef.getDefaultValue();
661 if (optDefaultValue.isPresent()) {
662 final var defaultValue = (String) optDefaultValue.orElseThrow();
663 return defaultValue.startsWith("0") || defaultValue.startsWith("-0");
668 private String processInstanceIdentifierType(final InstanceIdentifierTypeDefinition iidType,
669 final DataSchemaNode schemaNode, final EffectiveModelContext schemaContext,final TypeDef def) {
670 // create example instance-identifier to the first container of node's module if exists or leave it empty
671 final var module = schemaContext.findModule(schemaNode.getQName().getModule());
672 if (module.isPresent()) {
673 final var container = module.orElseThrow().getChildNodes().stream()
674 .filter(n -> n instanceof ContainerSchemaNode)
676 if (container.isPresent()) {
677 def.setExample(String.format("/%s:%s", module.orElseThrow().getPrefix(),
678 container.orElseThrow().getQName().getLocalName()));
682 if (iidType.getDefaultValue().isPresent()) {
683 def.setDefaultValue(iidType.getDefaultValue().orElseThrow().toString());