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);
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) throws IOException {
178 final var schemaNode = (SchemaNode) dataNode;
179 final var localName = schemaNode.getQName().getLocalName();
180 final var nodeName = parentName + "_" + localName;
182 final String discriminator;
183 if (!definitionNames.isListedNode(schemaNode)) {
184 final var parentNameConfigLocalName = parentName + "_" + localName;
185 final var names = List.of(parentNameConfigLocalName);
186 discriminator = definitionNames.pickDiscriminator(schemaNode, names);
188 discriminator = definitionNames.getDiscriminator(schemaNode);
191 processRef(nodeName, schemaNode, discriminator);
194 private void processRef(final String name, final SchemaNode schemaNode, String discriminator) throws IOException {
195 final var ref = COMPONENTS_PREFIX + name + discriminator;
196 if (schemaNode instanceof ListSchemaNode listNode) {
197 generator.writeStringField(TYPE, ARRAY_TYPE);
198 generator.writeObjectFieldStart(ITEMS);
199 generator.writeStringField("$ref", ref);
200 generator.writeEndObject();
201 generator.writeStringField(DESCRIPTION, schemaNode.getDescription().orElse(""));
203 if (listNode.getElementCountConstraint().isPresent()) {
204 final var minElements = listNode.getElementCountConstraint().orElseThrow().getMinElements();
205 final var maxElements = listNode.getElementCountConstraint().orElseThrow().getMaxElements();
206 if (minElements != null) {
207 createExamples(listNode, minElements);
208 generator.writeNumberField("minItems", minElements);
210 if (maxElements != null) {
211 generator.writeNumberField("maxItems", maxElements);
216 Description can't be added, because nothing allowed alongside $ref.
217 allOf is not an option, because ServiceNow can't parse it.
219 generator.writeStringField("$ref", ref);
223 private void createExamples(final ListSchemaNode schemaNode,
224 @NonNull final Integer minElements) throws IOException {
225 final var firstExampleMap = prepareFirstListExample(schemaNode);
226 final var examples = new ArrayList<Map<String, Object>>();
227 examples.add(firstExampleMap);
229 final var unqiueContraintsNameSet = schemaNode.getUniqueConstraints().stream()
230 .map(ModelStatement::argument)
231 .flatMap(uniqueSt -> uniqueSt.stream()
232 .map(schemaNI -> schemaNI.lastNodeIdentifier().getLocalName()))
233 .collect(Collectors.toSet());
234 final var keysNameSet = schemaNode.getKeyDefinition().stream()
235 .map(AbstractQName::getLocalName)
236 .collect(Collectors.toSet());
237 for (int i = 1; i < minElements; i++) {
238 final var exampleMap = new HashMap<String, Object>();
239 for (final var example : firstExampleMap.entrySet()) {
240 final Object exampleValue;
241 if (keysNameSet.contains(example.getKey()) || unqiueContraintsNameSet.contains(example.getKey())) {
242 exampleValue = editExample(example.getValue(), i);
244 exampleValue = example.getValue();
246 exampleMap.put(example.getKey(), exampleValue);
248 examples.add(exampleMap);
250 generator.writeArrayFieldStart(EXAMPLE);
251 for (final var example : examples) {
252 generator.writeStartObject();
253 for (final var elem : example.entrySet()) {
254 writeValue(elem.getKey(), elem.getValue());
256 generator.writeEndObject();
258 generator.writeEndArray();
261 private HashMap<String, Object> prepareFirstListExample(final ListSchemaNode schemaNode) {
262 final var childNodes = schemaNode.getChildNodes();
263 final var firstExampleMap = new HashMap<String, Object>();
264 // Cycle for each child node
265 for (final var childNode : childNodes) {
266 if (childNode instanceof TypedDataSchemaNode leafSchemaNode) {
267 final var def = new TypeDef();
268 processTypeDef(leafSchemaNode.getType(), leafSchemaNode, null, def);
269 if (def.hasExample()) {
270 firstExampleMap.put(leafSchemaNode.getQName().getLocalName(), def.getExample());
274 return firstExampleMap;
277 private Object editExample(final Object exampleValue, final int edit) {
278 if (exampleValue instanceof String string) {
279 return string + "_" + edit;
280 } else if (exampleValue instanceof Integer number) {
281 return number + edit;
282 } else if (exampleValue instanceof Long number) {
283 return number + edit;
284 } else if (exampleValue instanceof Decimal64 number) {
285 return Decimal64.valueOf(BigDecimal.valueOf(number.intValue() + edit));
290 private void processUnknownDataSchemaNode(final DataSchemaNode leafNode, final String name,
291 final XMLNamespace parentNamespace) throws IOException {
292 assert (leafNode instanceof AnydataSchemaNode || leafNode instanceof AnyxmlSchemaNode);
294 final var leafDescription = leafNode.getDescription().orElse("");
295 generator.writeStringField(DESCRIPTION, leafDescription);
297 final var localName = leafNode.getQName().getLocalName();
298 generator.writeStringField(EXAMPLE, String.format("<%s> ... </%s>", localName, localName));
299 generator.writeStringField(TYPE, STRING_TYPE);
300 if (!leafNode.getQName().getNamespace().equals(parentNamespace)) {
301 // If the parent is not from the same model, define the child XML namespace.
302 buildXmlParameter(leafNode);
304 processMandatory((MandatoryAware) leafNode, name, required);
307 private void processLeafListNode(final LeafListSchemaNode listNode, final SchemaInferenceStack stack)
309 generator.writeStringField(TYPE, ARRAY_TYPE);
311 Integer minElements = null;
312 final var optConstraint = listNode.getElementCountConstraint();
313 if (optConstraint.isPresent()) {
314 minElements = optConstraint.orElseThrow().getMinElements();
315 if (minElements != null) {
316 generator.writeNumberField("minItems", minElements);
318 final var maxElements = optConstraint.orElseThrow().getMaxElements();
319 if (maxElements != null) {
320 generator.writeNumberField("maxItems", maxElements);
323 final var def = new TypeDef();
324 processTypeDef(listNode.getType(), listNode, stack, def);
326 generator.writeObjectFieldStart(ITEMS);
327 processTypeDef(listNode.getType(), listNode, stack);
328 generator.writeEndObject();
329 generator.writeStringField(DESCRIPTION, listNode.getDescription().orElse(""));
331 if (def.hasExample() && minElements != null) {
332 final var listOfExamples = new ArrayList<>();
333 for (int i = 0; i < minElements; i++) {
334 listOfExamples.add(def.getExample());
336 generator.writeArrayFieldStart(EXAMPLE);
337 for (final var example : listOfExamples) {
338 generator.writeString(example.toString());
340 generator.writeEndArray();
344 private void processLeafNode(final LeafSchemaNode leafNode, final String jsonLeafName,
345 final SchemaInferenceStack stack, final XMLNamespace parentNamespace) throws IOException {
346 final var leafDescription = leafNode.getDescription().orElse("");
347 generator.writeStringField(DESCRIPTION, leafDescription);
348 processTypeDef(leafNode.getType(), leafNode, stack);
349 if (!leafNode.getQName().getNamespace().equals(parentNamespace)) {
350 // If the parent is not from the same model, define the child XML namespace.
351 buildXmlParameter(leafNode);
353 processMandatory(leafNode, jsonLeafName, required);
356 private static void processMandatory(final MandatoryAware node, final String nodeName,
357 final List<String> required) {
358 if (node.isMandatory()) {
359 required.add(nodeName);
363 private void buildXmlParameter(final SchemaNode schemaNode) throws IOException {
364 generator.writeObjectFieldStart("xml");
365 generator.writeStringField("name", schemaNode.getQName().getLocalName());
366 generator.writeStringField("namespace", schemaNode.getQName().getNamespace().toString());
367 generator.writeEndObject();
370 protected static boolean isSchemaNodeMandatory(final DataSchemaNode schemaNode) {
371 if (schemaNode instanceof ContainerSchemaNode containerNode) {
372 if (containerNode.isPresenceContainer()) {
375 for (final var childNode : containerNode.getChildNodes()) {
376 if (childNode instanceof MandatoryAware mandatoryAware && mandatoryAware.isMandatory()) {
381 // A list or leaf-list node with a "min-elements" statement with a value greater than zero.
382 return schemaNode instanceof ElementCountConstraintAware constraintAware
383 && constraintAware.getElementCountConstraint()
384 .map(ElementCountConstraint::getMinElements)
388 private void processTypeDef(final TypeDefinition<?> leafTypeDef, final DataSchemaNode schemaNode,
389 final SchemaInferenceStack stack) throws IOException {
390 final var def = new TypeDef();
392 final var jsonType = processTypeDef(leafTypeDef, schemaNode, stack, def);
394 generator.writeStringField(TYPE, jsonType);
395 generateTypeDef(def);
398 private String processTypeDef(final TypeDefinition<?> leafTypeDef, final DataSchemaNode schemaNode,
399 final SchemaInferenceStack stack, final TypeDef def) {
400 final String jsonType;
401 if (leafTypeDef instanceof BinaryTypeDefinition binaryType) {
402 jsonType = processBinaryType(binaryType, def);
403 } else if (leafTypeDef instanceof BitsTypeDefinition bitsType) {
404 jsonType = processBitsType(bitsType, def);
405 } else if (leafTypeDef instanceof EnumTypeDefinition enumType) {
406 jsonType = processEnumType(enumType, def);
407 } else if (leafTypeDef instanceof IdentityrefTypeDefinition identityrefType) {
408 jsonType = processIdentityRefType(identityrefType, stack.modelContext(), def);
409 } else if (leafTypeDef instanceof StringTypeDefinition stringType) {
410 jsonType = processStringType(stringType, schemaNode.getQName().getLocalName(), def);
411 } else if (leafTypeDef instanceof UnionTypeDefinition unionType) {
412 jsonType = processTypeDef(unionType.getTypes().iterator().next(), schemaNode, stack, def);
413 } else if (leafTypeDef instanceof EmptyTypeDefinition) {
414 jsonType = OBJECT_TYPE;
415 } else if (leafTypeDef instanceof LeafrefTypeDefinition leafrefType) {
416 jsonType = processTypeDef(stack.resolveLeafref(leafrefType), schemaNode, stack, def);
417 } else if (leafTypeDef instanceof BooleanTypeDefinition) {
418 jsonType = "boolean";
419 if (leafTypeDef.getDefaultValue().isPresent()) {
420 def.setDefaultValue(Boolean.parseBoolean((String) leafTypeDef.getDefaultValue().orElseThrow()));
422 def.setExample(true);
423 } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition<?, ?> rangeRestrictedType) {
424 jsonType = processNumberType(rangeRestrictedType, def);
425 } else if (leafTypeDef instanceof InstanceIdentifierTypeDefinition instanceIdentifierType) {
426 jsonType = processInstanceIdentifierType(instanceIdentifierType, schemaNode,
427 stack.modelContext(), def);
429 jsonType = STRING_TYPE;
432 if (leafTypeDef.getDefaultValue().isPresent()) {
433 final var defaultValue = leafTypeDef.getDefaultValue().orElseThrow();
434 if (defaultValue instanceof String stringDefaultValue) {
435 if (leafTypeDef instanceof BooleanTypeDefinition) {
436 def.setDefaultValue(Boolean.valueOf(stringDefaultValue));
437 } else if (leafTypeDef instanceof DecimalTypeDefinition
438 || leafTypeDef instanceof Uint64TypeDefinition) {
439 def.setDefaultValue(new BigDecimal(stringDefaultValue));
440 } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition<?, ?> rangeRestrictedType) {
441 //uint8,16,32 int8,16,32,64
442 if (isHexadecimalOrOctal(rangeRestrictedType)) {
443 def.setDefaultValue(stringDefaultValue);
445 def.setDefaultValue(Long.valueOf(stringDefaultValue));
448 def.setDefaultValue(stringDefaultValue);
455 private void writeValue(final String field, final Object value) throws IOException {
456 if (value instanceof Number) {
457 if (value instanceof Integer intValue) {
458 generator.writeNumberField(field, intValue);
459 } else if (value instanceof Long longValue) {
460 generator.writeNumberField(field, longValue);
461 } else if (value instanceof BigDecimal decimalValue) {
462 generator.writeNumberField(field, decimalValue);
463 } else if (value instanceof BigInteger bigIntValue) {
464 generator.writeNumberField(field, bigIntValue);
466 } else if (value instanceof Boolean bool) {
467 generator.writeBooleanField(field, bool);
469 generator.writeStringField(field, (String) value);
473 private void generateTypeDef(final TypeDef def) throws IOException {
474 if (def.hasEnums()) {
475 generator.writeArrayFieldStart(ENUM);
476 for (final var enumElem : def.getEnums()) {
477 generator.writeString(enumElem);
479 generator.writeEndArray();
481 if (def.hasFormat()) {
482 generator.writeStringField(FORMAT, def.getFormat());
484 if (def.hasMinItems()) {
485 generator.writeNumberField("minItems", def.getMinItems());
487 if (def.hasDefaultValue()) {
488 writeValue(DEFAULT, def.getDefaultValue());
490 if (def.hasExample()) {
491 writeValue(EXAMPLE, def.getExample());
493 if (def.hasUniqueItems()) {
494 generator.writeBooleanField("uniqueItems", def.getUniqueItems());
496 if (def.hasMinLength()) {
497 generator.writeNumberField("minLength", def.getMinLength());
499 if (def.hasMaxLength()) {
500 generator.writeNumberField("maxLength", def.getMaxLength());
504 private String processBinaryType(final BinaryTypeDefinition definition, final TypeDef def) {
505 if (definition.getDefaultValue().isPresent()) {
506 def.setDefaultValue(definition.getDefaultValue().toString());
508 def.setFormat("byte");
512 private String processBitsType(final BitsTypeDefinition bitsType, final TypeDef def) {
514 def.setUniqueItems(true);
515 final var bits = bitsType.getBits();
516 final var enumNames = bits.stream()
517 .map(BitsTypeDefinition.Bit::getName)
520 def.setEnums(enumNames);
522 def.setDefaultValue(bitsType.getDefaultValue().isPresent()
523 ? bitsType.getDefaultValue().orElseThrow().toString() :
524 (enumNames.iterator().next() + " " + enumNames.get(enumNames.size() - 1)));
528 private String processEnumType(final EnumTypeDefinition enumLeafType, final TypeDef def) {
529 final var enumPairs = enumLeafType.getValues();
530 final var enumNames = enumPairs.stream()
531 .map(EnumTypeDefinition.EnumPair::getName)
534 def.setEnums(enumNames);
536 if (enumLeafType.getDefaultValue().isPresent()) {
537 final var defaultValue = enumLeafType.getDefaultValue().orElseThrow().toString();
538 def.setDefaultValue(defaultValue);
540 def.setExample(enumLeafType.getValues().iterator().next().getName());
544 private String processIdentityRefType(final IdentityrefTypeDefinition leafTypeDef,
545 final EffectiveModelContext schemaContext, final TypeDef def) {
546 final var schemaNode = leafTypeDef.getIdentities().iterator().next();
547 def.setExample(schemaNode.getQName().getLocalName());
549 final var derivedIds = schemaContext.getDerivedIdentities(schemaNode);
550 final var enumPayload = new ArrayList<String>();
551 enumPayload.add(schemaNode.getQName().getLocalName());
552 populateEnumWithDerived(derivedIds, enumPayload, schemaContext);
553 final var schemaEnum = new ArrayList<>(enumPayload);
555 def.setEnums(schemaEnum);
560 private void populateEnumWithDerived(final Collection<? extends IdentitySchemaNode> derivedIds,
561 final List<String> enumPayload, final EffectiveModelContext context) {
562 for (final var derivedId : derivedIds) {
563 enumPayload.add(derivedId.getQName().getLocalName());
564 populateEnumWithDerived(context.getDerivedIdentities(derivedId), enumPayload, context);
568 private String processStringType(final StringTypeDefinition stringType, final String nodeName, final TypeDef def) {
569 var type = stringType;
570 while (type.getLengthConstraint().isEmpty() && type.getBaseType() != null) {
571 type = type.getBaseType();
574 if (type.getLengthConstraint().isPresent()) {
575 final var range = type.getLengthConstraint().orElseThrow().getAllowedRanges().span();
576 def.setMinLength(range.lowerEndpoint());
577 def.setMaxLength(range.upperEndpoint());
580 if (type.getPatternConstraints().iterator().hasNext()) {
581 final PatternConstraint pattern = type.getPatternConstraints().iterator().next();
582 var regex = pattern.getRegularExpressionString();
583 // Escape special characters to prevent issues inside Automaton.
584 regex = AUTOMATON_SPECIAL_CHARACTERS.matcher(regex).replaceAll("\\\\$0");
585 for (final var charClass : PREDEFINED_CHARACTER_CLASSES.entrySet()) {
586 regex = regex.replaceAll(charClass.getKey(), charClass.getValue());
588 var defaultValue = "";
590 final var regExp = new RegExp(regex);
591 defaultValue = regExp.toAutomaton().getShortestExample(true);
592 } catch (IllegalArgumentException ex) {
593 LOG.warn("Cannot create example string for type: {} with regex: {}.", stringType.getQName(), regex);
595 def.setExample(defaultValue);
597 def.setExample("Some " + nodeName);
600 if (stringType.getDefaultValue().isPresent()) {
601 def.setDefaultValue(stringType.getDefaultValue().orElseThrow().toString());
606 private String processNumberType(final RangeRestrictedTypeDefinition<?, ?> leafTypeDef,final TypeDef def) {
607 final var maybeLower = leafTypeDef.getRangeConstraint()
608 .map(RangeConstraint::getAllowedRanges).map(RangeSet::span).map(Range::lowerEndpoint);
610 if (isHexadecimalOrOctal(leafTypeDef)) {
614 if (leafTypeDef instanceof DecimalTypeDefinition) {
615 if (leafTypeDef.getDefaultValue().isPresent()) {
616 def.setDefaultValue(Decimal64.valueOf(
617 leafTypeDef.getDefaultValue().orElseThrow().toString()).decimalValue());
619 if (maybeLower.isPresent()) {
620 def.setExample(((Decimal64) maybeLower.orElseThrow()).decimalValue());
624 if (leafTypeDef instanceof Uint8TypeDefinition
625 || leafTypeDef instanceof Uint16TypeDefinition
626 || leafTypeDef instanceof Int8TypeDefinition
627 || leafTypeDef instanceof Int16TypeDefinition
628 || leafTypeDef instanceof Int32TypeDefinition) {
630 def.setFormat("int32");
631 if (leafTypeDef.getDefaultValue().isPresent()) {
632 def.setDefaultValue(Integer.parseInt(leafTypeDef.getDefaultValue().orElseThrow().toString()));
634 if (maybeLower.isPresent()) {
635 def.setExample(Integer.parseInt(maybeLower.orElseThrow().toString()));
637 } else if (leafTypeDef instanceof Uint32TypeDefinition
638 || leafTypeDef instanceof Int64TypeDefinition) {
639 def.setFormat("int64");
640 if (leafTypeDef.getDefaultValue().isPresent()) {
641 def.setDefaultValue(Long.parseLong(leafTypeDef.getDefaultValue().orElseThrow().toString()));
643 if (maybeLower.isPresent()) {
644 def.setExample(Long.parseLong(maybeLower.orElseThrow().toString()));
648 if (leafTypeDef.getDefaultValue().isPresent()) {
649 def.setDefaultValue(new BigInteger(leafTypeDef.getDefaultValue().orElseThrow().toString()));
656 private static boolean isHexadecimalOrOctal(final RangeRestrictedTypeDefinition<?, ?> typeDef) {
657 final var optDefaultValue = typeDef.getDefaultValue();
658 if (optDefaultValue.isPresent()) {
659 final var defaultValue = (String) optDefaultValue.orElseThrow();
660 return defaultValue.startsWith("0") || defaultValue.startsWith("-0");
665 private String processInstanceIdentifierType(final InstanceIdentifierTypeDefinition iidType,
666 final DataSchemaNode schemaNode, final EffectiveModelContext schemaContext,final TypeDef def) {
667 // create example instance-identifier to the first container of node's module if exists or leave it empty
668 final var module = schemaContext.findModule(schemaNode.getQName().getModule());
669 if (module.isPresent()) {
670 final var container = module.orElseThrow().getChildNodes().stream()
671 .filter(n -> n instanceof ContainerSchemaNode)
673 if (container.isPresent()) {
674 def.setExample(String.format("/%s:%s", module.orElseThrow().getPrefix(),
675 container.orElseThrow().getQName().getLocalName()));
679 if (iidType.getDefaultValue().isPresent()) {
680 def.setDefaultValue(iidType.getDefaultValue().orElseThrow().toString());