2 * Copyright (c) 2014 Cisco Systems, Inc. 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.netconf.sal.rest.doc.impl;
10 import static org.opendaylight.netconf.sal.rest.doc.util.RestDocgenUtil.resolveNodesName;
12 import com.fasterxml.jackson.databind.JsonNode;
13 import com.fasterxml.jackson.databind.node.ArrayNode;
14 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
15 import com.fasterxml.jackson.databind.node.ObjectNode;
16 import com.fasterxml.jackson.databind.node.TextNode;
17 import com.google.common.collect.Range;
18 import com.google.common.collect.RangeSet;
19 import com.mifmif.common.regex.Generex;
20 import java.io.IOException;
21 import java.util.Collection;
22 import java.util.List;
23 import java.util.Optional;
24 import java.util.regex.Pattern;
25 import org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder;
26 import org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.Post;
27 import org.opendaylight.yangtools.yang.common.QName;
28 import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
29 import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer;
30 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
35 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.ElementCountConstraint;
37 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.MandatoryAware;
42 import org.opendaylight.yangtools.yang.model.api.Module;
43 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
44 import org.opendaylight.yangtools.yang.model.api.PathExpression;
45 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
46 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
47 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
49 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
50 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
51 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
52 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition.Bit;
53 import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
54 import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition;
55 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
56 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition.EnumPair;
57 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
58 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
59 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
60 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
61 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
62 import org.opendaylight.yangtools.yang.model.api.type.RangeRestrictedTypeDefinition;
63 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
64 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
65 import org.opendaylight.yangtools.yang.model.util.PathExpressionImpl;
66 import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
71 * Generates JSON Schema for data defined in YANG. This class is not thread-safe.
73 public class ModelGenerator {
75 private static final Logger LOG = LoggerFactory.getLogger(ModelGenerator.class);
77 private static final Pattern STRIP_PATTERN = Pattern.compile("\\[[^\\[\\]]*\\]");
78 private static final String BASE_64 = "base64";
79 private static final String BINARY_ENCODING_KEY = "binaryEncoding";
80 private static final String MEDIA_KEY = "media";
81 private static final String UNIQUE_ITEMS_KEY = "uniqueItems";
82 private static final String MAX_ITEMS = "maxItems";
83 private static final String MIN_ITEMS = "minItems";
84 private static final String SCHEMA_URL = "http://json-schema.org/draft-04/schema";
85 private static final String SCHEMA_KEY = "$schema";
86 private static final String MAX_LENGTH_KEY = "maxLength";
87 private static final String MIN_LENGTH_KEY = "minLength";
88 private static final String REQUIRED_KEY = "required";
89 private static final String REF_KEY = "$ref";
90 private static final String ITEMS_KEY = "items";
91 private static final String TYPE_KEY = "type";
92 private static final String PROPERTIES_KEY = "properties";
93 private static final String DESCRIPTION_KEY = "description";
94 private static final String OBJECT_TYPE = "object";
95 private static final String ARRAY_TYPE = "array";
96 private static final String ENUM = "enum";
97 private static final String ID_KEY = "id";
98 private static final String SUB_TYPES_KEY = "subTypes";
99 private static final String UNIQUE_EMPTY_IDENTIFIER = "unique_empty_identifier";
101 private Module topLevelModule;
103 public ModelGenerator() {
107 * Creates Json models from provided module according to swagger spec.
109 * @param module - Yang module to be converted
110 * @param schemaContext - SchemaContext of all Yang files used by Api Doc
111 * @return ObjectNode containing data used for creating examples and models in Api Doc
112 * @throws IOException if I/O operation fails
114 public ObjectNode convertToJsonSchema(final Module module,
115 final SchemaContext schemaContext) throws IOException {
116 final ObjectNode models = JsonNodeFactory.instance.objectNode();
117 final ObjectNode emptyIdentifier = JsonNodeFactory.instance.objectNode();
118 models.set(UNIQUE_EMPTY_IDENTIFIER, emptyIdentifier);
119 topLevelModule = module;
120 processModules(module, models, schemaContext);
121 processContainersAndLists(module, models, schemaContext);
122 processRPCs(module, models, schemaContext);
123 processIdentities(schemaContext, module, models);
127 private void processModules(final Module module, final ObjectNode models,
128 final SchemaContext schemaContext) {
129 createConcreteModelForPost(models, module.getName() + BaseYangSwaggerGenerator.MODULE_NAME_SUFFIX,
130 createPropertiesForPost(module, schemaContext, module.getName()));
133 private void processContainersAndLists(final Module module, final ObjectNode models,
134 final SchemaContext schemaContext) throws IOException {
135 final String moduleName = module.getName();
137 for (final DataSchemaNode childNode : module.getChildNodes()) {
138 // For every container and list in the module
139 if (childNode instanceof ContainerSchemaNode || childNode instanceof ListSchemaNode) {
140 processDataNodeContainer((DataNodeContainer) childNode, moduleName, models, true, schemaContext);
141 processDataNodeContainer((DataNodeContainer) childNode, moduleName, models, false, schemaContext);
142 processActionNodeContainer(childNode, moduleName, models, schemaContext);
148 * Process the Actions Swagger UI.
150 * @param DataSchemaNode childNode
151 * @param String moduleName
152 * @param ObjectNode models
153 * @param SchemaContext schemaContext
154 * @throws IOException if I/O operation fails
156 private void processActionNodeContainer(final DataSchemaNode childNode, final String moduleName,
157 final ObjectNode models, final SchemaContext schemaContext) throws IOException {
158 for (ActionDefinition actionDef : ((ActionNodeContainer) childNode).getActions()) {
159 processOperations(actionDef, moduleName, models, schemaContext);
164 * Process the RPCs for Swagger UI.
166 * @param Module module
167 * @param ObjectNode models
168 * @param SchemaContext schemaContext
169 * @throws IOException if I/O operation fails
171 private void processRPCs(final Module module, final ObjectNode models, final SchemaContext schemaContext)
173 final String moduleName = module.getName();
174 for (final RpcDefinition rpcDefinition : module.getRpcs()) {
175 processOperations(rpcDefinition, moduleName, models, schemaContext);
180 * Process the Operations for a Module Spits out a file each of the name {@code <operationName>-input.json and
181 * <oprationName>-output.json} for each Operation that contains input & output elements.
183 * @param OperationDefinition operationDef
184 * @param String moduleName
185 * @param ObjectNode models
186 * @param SchemaContext schemaContext
187 * @throws IOException if I/O operation fails
189 private void processOperations(final OperationDefinition operationDef, final String moduleName,
190 final ObjectNode models, final SchemaContext schemaContext) throws IOException {
191 final ContainerSchemaNode input = operationDef.getInput();
192 if (!input.getChildNodes().isEmpty()) {
193 final ObjectNode properties =
194 processChildren(input.getChildNodes(), moduleName, models, true, schemaContext);
196 final String filename = "(" + operationDef.getQName().getLocalName() + ")input";
197 final ObjectNode childSchema = getSchemaTemplate();
198 childSchema.put(TYPE_KEY, OBJECT_TYPE);
199 childSchema.set(PROPERTIES_KEY, properties);
200 childSchema.put(ID_KEY, filename);
201 models.set(filename, childSchema);
203 processTopData(filename, models, input);
206 final ContainerSchemaNode output = operationDef.getOutput();
207 if (!output.getChildNodes().isEmpty()) {
208 final ObjectNode properties =
209 processChildren(output.getChildNodes(), moduleName, models, true, schemaContext);
210 final String filename = "(" + operationDef.getQName().getLocalName() + ")output";
211 final ObjectNode childSchema = getSchemaTemplate();
212 childSchema.put(TYPE_KEY, OBJECT_TYPE);
213 childSchema.set(PROPERTIES_KEY, properties);
214 childSchema.put(ID_KEY, filename);
215 models.set(filename, childSchema);
217 processTopData(filename, models, output);
221 private ObjectNode processTopData(final String filename, final ObjectNode models, final SchemaNode schemaNode) {
222 final ObjectNode items = JsonNodeFactory.instance.objectNode();
224 items.put(REF_KEY, filename);
225 final ObjectNode dataNodeProperties = JsonNodeFactory.instance.objectNode();
226 dataNodeProperties.put(TYPE_KEY, schemaNode instanceof ListSchemaNode ? ARRAY_TYPE : OBJECT_TYPE);
227 dataNodeProperties.set(ITEMS_KEY, items);
229 putIfNonNull(dataNodeProperties, DESCRIPTION_KEY, schemaNode.getDescription().orElse(null));
230 final ObjectNode properties = JsonNodeFactory.instance.objectNode();
231 properties.set(topLevelModule.getName() + ":" + schemaNode.getQName().getLocalName(), dataNodeProperties);
232 final ObjectNode finalChildSchema = getSchemaTemplate();
233 finalChildSchema.put(TYPE_KEY, OBJECT_TYPE);
234 finalChildSchema.set(PROPERTIES_KEY, properties);
235 finalChildSchema.put(ID_KEY, filename + OperationBuilder.TOP);
236 models.set(filename + OperationBuilder.TOP, finalChildSchema);
238 return dataNodeProperties;
242 * Processes the 'identity' statement in a yang model and maps it to a 'model' in the Swagger JSON spec.
244 * @param module The module from which the identity stmt will be processed
245 * @param models The ObjectNode in which the parsed identity will be put as a 'model' obj
247 private static void processIdentities(final SchemaContext schemaContext, final Module module,
248 final ObjectNode models) {
250 final String moduleName = module.getName();
251 final Collection<? extends IdentitySchemaNode> idNodes = module.getIdentities();
252 LOG.debug("Processing Identities for module {} . Found {} identity statements", moduleName, idNodes.size());
254 for (final IdentitySchemaNode idNode : idNodes) {
255 final ObjectNode identityObj = JsonNodeFactory.instance.objectNode();
256 final String identityName = idNode.getQName().getLocalName();
257 LOG.debug("Processing Identity: {}", identityName);
259 identityObj.put(ID_KEY, identityName);
260 putIfNonNull(identityObj, DESCRIPTION_KEY, idNode.getDescription().orElse(null));
262 final ObjectNode props = JsonNodeFactory.instance.objectNode();
264 if (idNode.getBaseIdentities().isEmpty()) {
266 * This is a base identity. So lets see if it has sub types. If it does, then add them to the model
269 final Collection<? extends IdentitySchemaNode> derivedIds = schemaContext.getDerivedIdentities(idNode);
270 if (!derivedIds.isEmpty()) {
271 final ArrayNode subTypes = new ArrayNode(JsonNodeFactory.instance);
272 for (final IdentitySchemaNode derivedId : derivedIds) {
273 subTypes.add(derivedId.getQName().getLocalName());
275 identityObj.set(SUB_TYPES_KEY, subTypes);
279 * This is a derived entity. Add it's base type & move on.
281 props.put(TYPE_KEY, idNode.getBaseIdentities().iterator().next().getQName().getLocalName());
284 // Add the properties. For a base type, this will be an empty object as required by the Swagger spec.
285 identityObj.set(PROPERTIES_KEY, props);
286 models.set(identityName, identityObj);
290 private ObjectNode processDataNodeContainer(
291 final DataNodeContainer dataNode, final String parentName, final ObjectNode models, final boolean isConfig,
292 final SchemaContext schemaContext) throws IOException {
293 if (dataNode instanceof ListSchemaNode || dataNode instanceof ContainerSchemaNode) {
294 final String localName = ((SchemaNode) dataNode).getQName().getLocalName();
295 final ObjectNode properties = processChildren(dataNode.getChildNodes(), parentName + "/" + localName,
296 models, isConfig, schemaContext);
297 final String nodeName = parentName + (isConfig ? OperationBuilder.CONFIG : OperationBuilder.OPERATIONAL)
300 final ObjectNode childSchema = getSchemaTemplate();
301 childSchema.put(TYPE_KEY, OBJECT_TYPE);
302 childSchema.set(PROPERTIES_KEY, properties);
304 childSchema.put(ID_KEY, nodeName);
305 models.set(nodeName, childSchema);
308 createConcreteModelForPost(models, localName,
309 createPropertiesForPost(dataNode, schemaContext, parentName + "/" + localName));
312 return processTopData(nodeName, models, (SchemaNode) dataNode);
317 private static void createConcreteModelForPost(final ObjectNode models, final String localName,
318 final JsonNode properties) {
319 final String nodePostName = OperationBuilder.CONFIG + localName + Post.METHOD_NAME;
320 final ObjectNode postSchema = getSchemaTemplate();
321 postSchema.put(TYPE_KEY, OBJECT_TYPE);
322 postSchema.put(ID_KEY, nodePostName);
323 postSchema.set(PROPERTIES_KEY, properties);
324 models.set(nodePostName, postSchema);
327 private JsonNode createPropertiesForPost(final DataNodeContainer dataNodeContainer,
328 final SchemaContext schemaContext, final String parentName) {
329 final ObjectNode properties = JsonNodeFactory.instance.objectNode();
330 for (final DataSchemaNode childNode : dataNodeContainer.getChildNodes()) {
331 if (childNode instanceof ListSchemaNode || childNode instanceof ContainerSchemaNode) {
332 final ObjectNode items = JsonNodeFactory.instance.objectNode();
333 items.put(REF_KEY, parentName + "(config)" + childNode.getQName().getLocalName());
334 final ObjectNode property = JsonNodeFactory.instance.objectNode();
335 property.put(TYPE_KEY, childNode instanceof ListSchemaNode ? ARRAY_TYPE : OBJECT_TYPE);
336 property.set(ITEMS_KEY, items);
337 properties.set(childNode.getQName().getLocalName(), property);
338 } else if (childNode instanceof LeafSchemaNode) {
339 final ObjectNode property = processLeafNode((LeafSchemaNode) childNode, schemaContext);
340 properties.set(childNode.getQName().getLocalName(), property);
347 * Processes the nodes.
349 private ObjectNode processChildren(
350 final Iterable<? extends DataSchemaNode> nodes, final String parentName, final ObjectNode models,
351 final boolean isConfig, final SchemaContext schemaContext) throws IOException {
352 final ObjectNode properties = JsonNodeFactory.instance.objectNode();
353 for (final DataSchemaNode node : nodes) {
354 if (node.isConfiguration() == isConfig) {
355 final String name = resolveNodesName(node, topLevelModule, schemaContext);
356 final ObjectNode property;
357 if (node instanceof LeafSchemaNode) {
358 property = processLeafNode((LeafSchemaNode) node, schemaContext);
360 } else if (node instanceof ListSchemaNode) {
361 property = processDataNodeContainer((ListSchemaNode) node, parentName, models, isConfig,
364 } else if (node instanceof LeafListSchemaNode) {
365 property = processLeafListNode((LeafListSchemaNode) node, schemaContext);
367 } else if (node instanceof ChoiceSchemaNode) {
368 if (((ChoiceSchemaNode) node).getCases().iterator().hasNext()) {
369 processChoiceNode(((ChoiceSchemaNode) node).getCases().iterator().next()
370 .getChildNodes(), parentName, models, schemaContext, isConfig, properties);
374 } else if (node instanceof AnyxmlSchemaNode) {
375 property = processAnyXMLNode((AnyxmlSchemaNode) node);
377 } else if (node instanceof AnydataSchemaNode) {
378 property = processAnydataNode((AnydataSchemaNode) node);
380 } else if (node instanceof ContainerSchemaNode) {
381 property = processDataNodeContainer((ContainerSchemaNode) node, parentName, models, isConfig,
385 throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
387 putIfNonNull(property, DESCRIPTION_KEY, node.getDescription().orElse(null));
388 properties.set(topLevelModule.getName() + ":" + name, property);
394 private ObjectNode processLeafListNode(final LeafListSchemaNode listNode,
395 final SchemaContext schemaContext) {
396 final ObjectNode props = JsonNodeFactory.instance.objectNode();
397 props.put(TYPE_KEY, ARRAY_TYPE);
399 final ObjectNode itemsVal = JsonNodeFactory.instance.objectNode();
400 final Optional<ElementCountConstraint> optConstraint = listNode.getElementCountConstraint();
402 if (optConstraint.isPresent()) {
403 final Integer constraintMax = optConstraint.get().getMaxElements();
404 max = constraintMax == null ? 2 : constraintMax;
405 processElementCount(optConstraint.get(), props);
411 processTypeDef(listNode.getType(), listNode, itemsVal, schemaContext);
412 processTypeDef(listNode.getType(), listNode, itemsVal, schemaContext);
414 processTypeDef(listNode.getType(), listNode, itemsVal, schemaContext);
416 props.set(ITEMS_KEY, itemsVal);
421 private void processChoiceNode(
422 final Iterable<? extends DataSchemaNode> nodes, final String moduleName, final ObjectNode models,
423 final SchemaContext schemaContext, final boolean isConfig, final ObjectNode properties)
425 for (final DataSchemaNode node : nodes) {
426 final String name = resolveNodesName(node, topLevelModule, schemaContext);
427 final ObjectNode property;
429 if (node instanceof LeafSchemaNode) {
430 property = processLeafNode((LeafSchemaNode) node, schemaContext);
432 } else if (node instanceof ListSchemaNode) {
433 property = processDataNodeContainer((ListSchemaNode) node, moduleName, models, isConfig,
436 } else if (node instanceof LeafListSchemaNode) {
437 property = processLeafListNode((LeafListSchemaNode) node, schemaContext);
439 } else if (node instanceof ChoiceSchemaNode) {
440 if (((ChoiceSchemaNode) node).getCases().iterator().hasNext()) {
441 processChoiceNode(((ChoiceSchemaNode) node).getCases().iterator().next().getChildNodes(),
442 moduleName, models, schemaContext, isConfig, properties);
446 } else if (node instanceof AnyxmlSchemaNode) {
447 property = processAnyXMLNode((AnyxmlSchemaNode) node);
449 } else if (node instanceof AnydataSchemaNode) {
450 property = processAnydataNode((AnydataSchemaNode) node);
452 } else if (node instanceof ContainerSchemaNode) {
453 property = processDataNodeContainer((ContainerSchemaNode) node, moduleName, models, isConfig,
457 throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
460 putIfNonNull(property, DESCRIPTION_KEY, node.getDescription().orElse(null));
461 properties.set(name, property);
465 private static void processElementCount(final ElementCountConstraint constraint, final ObjectNode props) {
466 final Integer minElements = constraint.getMinElements();
467 if (minElements != null) {
468 props.put(MIN_ITEMS, minElements);
470 final Integer maxElements = constraint.getMaxElements();
471 if (maxElements != null) {
472 props.put(MAX_ITEMS, maxElements);
476 private static void processMandatory(final MandatoryAware node, final ObjectNode props) {
477 props.put(REQUIRED_KEY, node.isMandatory());
480 private ObjectNode processLeafNode(final LeafSchemaNode leafNode,
481 final SchemaContext schemaContext) {
482 final ObjectNode property = JsonNodeFactory.instance.objectNode();
484 final String leafDescription = leafNode.getDescription().orElse(null);
485 putIfNonNull(property, DESCRIPTION_KEY, leafDescription);
486 processMandatory(leafNode, property);
487 processTypeDef(leafNode.getType(), leafNode, property, schemaContext);
492 private static ObjectNode processAnydataNode(final AnydataSchemaNode leafNode) {
493 final ObjectNode property = JsonNodeFactory.instance.objectNode();
495 final String leafDescription = leafNode.getDescription().orElse(null);
496 putIfNonNull(property, DESCRIPTION_KEY, leafDescription);
498 processMandatory(leafNode, property);
499 final String localName = leafNode.getQName().getLocalName();
500 property.put(TYPE_KEY, "example of anydata " + localName);
505 private static ObjectNode processAnyXMLNode(final AnyxmlSchemaNode leafNode) {
506 final ObjectNode property = JsonNodeFactory.instance.objectNode();
508 final String leafDescription = leafNode.getDescription().orElse(null);
509 putIfNonNull(property, DESCRIPTION_KEY, leafDescription);
511 processMandatory(leafNode, property);
512 final String localName = leafNode.getQName().getLocalName();
513 property.put(TYPE_KEY, "example of anyxml " + localName);
518 private String processTypeDef(final TypeDefinition<?> leafTypeDef, final DataSchemaNode node,
519 final ObjectNode property, final SchemaContext schemaContext) {
520 final String jsonType;
521 final Object defValue = leafTypeDef.getDefaultValue().orElse(null);
522 if (defValue == null) {
523 if (leafTypeDef instanceof BinaryTypeDefinition) {
524 jsonType = processBinaryType(property);
526 } else if (leafTypeDef instanceof BitsTypeDefinition) {
527 jsonType = processBitsType((BitsTypeDefinition) leafTypeDef, property);
529 } else if (leafTypeDef instanceof EnumTypeDefinition) {
530 jsonType = processEnumType((EnumTypeDefinition) leafTypeDef, property);
532 } else if (leafTypeDef instanceof IdentityrefTypeDefinition) {
533 final String name = topLevelModule.getName();
534 jsonType = name + ":" + ((IdentityrefTypeDefinition) leafTypeDef).getIdentities().iterator().next()
535 .getQName().getLocalName();
537 } else if (leafTypeDef instanceof StringTypeDefinition) {
538 jsonType = processStringType(leafTypeDef, property, node.getQName().getLocalName());
540 } else if (leafTypeDef instanceof UnionTypeDefinition) {
541 jsonType = processUnionType((UnionTypeDefinition) leafTypeDef, property, schemaContext, node);
543 } else if (leafTypeDef instanceof EmptyTypeDefinition) {
544 jsonType = UNIQUE_EMPTY_IDENTIFIER;
546 } else if (leafTypeDef instanceof LeafrefTypeDefinition) {
547 return processLeafRef(node, property, schemaContext, leafTypeDef);
549 } else if (leafTypeDef instanceof BooleanTypeDefinition) {
552 } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
553 final Number maybeLower = ((RangeRestrictedTypeDefinition<?, ?>) leafTypeDef).getRangeConstraint()
554 .map(RangeConstraint::getAllowedRanges).map(RangeSet::span).map(Range::lowerEndpoint)
556 jsonType = String.valueOf(maybeLower);
559 jsonType = OBJECT_TYPE;
563 jsonType = String.valueOf(defValue);
565 putIfNonNull(property, TYPE_KEY, jsonType);
569 private String processLeafRef(final DataSchemaNode node, final ObjectNode property,
570 final SchemaContext schemaContext, final TypeDefinition<?> leafTypeDef) {
571 PathExpression xpath = ((LeafrefTypeDefinition) leafTypeDef).getPathStatement();
572 final SchemaNode schemaNode;
574 final String xPathString = STRIP_PATTERN.matcher(xpath.getOriginalString()).replaceAll("");
575 xpath = new PathExpressionImpl(xPathString, xpath.isAbsolute());
578 if (xpath.isAbsolute()) {
579 module = findModule(schemaContext, leafTypeDef.getQName());
580 schemaNode = SchemaContextUtil.findDataSchemaNode(schemaContext, module, xpath);
582 module = findModule(schemaContext, node.getQName());
583 schemaNode = SchemaContextUtil.findDataSchemaNodeForRelativeXPath(schemaContext, module, node, xpath);
586 return processTypeDef(((TypedDataSchemaNode) schemaNode).getType(), (DataSchemaNode) schemaNode,
587 property, schemaContext);
590 private static Module findModule(final SchemaContext schemaContext, final QName qualifiedName) {
591 return schemaContext.findModule(qualifiedName.getNamespace(), qualifiedName.getRevision()).orElse(null);
594 private static String processBinaryType(final ObjectNode property) {
595 final ObjectNode media = JsonNodeFactory.instance.objectNode();
596 media.put(BINARY_ENCODING_KEY, BASE_64);
597 property.set(MEDIA_KEY, media);
601 private static String processEnumType(final EnumTypeDefinition enumLeafType,
602 final ObjectNode property) {
603 final List<EnumPair> enumPairs = enumLeafType.getValues();
604 ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
605 for (final EnumPair enumPair : enumPairs) {
606 enumNames.add(new TextNode(enumPair.getName()));
609 property.set(ENUM, enumNames);
610 return enumLeafType.getValues().iterator().next().getName();
613 private static String processBitsType(final BitsTypeDefinition bitsType,
614 final ObjectNode property) {
615 property.put(MIN_ITEMS, 0);
616 property.put(UNIQUE_ITEMS_KEY, true);
617 ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
618 for (final Bit bit : bitsType.getBits()) {
619 enumNames.add(new TextNode(bit.getName()));
621 property.set(ENUM, enumNames);
623 return enumNames.iterator().next() + " " + enumNames.get(enumNames.size() - 1);
626 private static String processStringType(final TypeDefinition<?> stringType,
627 final ObjectNode property, final String nodeName) {
628 StringTypeDefinition type = (StringTypeDefinition) stringType;
629 Optional<LengthConstraint> lengthConstraints = ((StringTypeDefinition) stringType).getLengthConstraint();
630 while (!lengthConstraints.isPresent() && type.getBaseType() != null) {
631 type = type.getBaseType();
632 lengthConstraints = type.getLengthConstraint();
635 if (lengthConstraints.isPresent()) {
636 final Range<Integer> range = lengthConstraints.get().getAllowedRanges().span();
637 putIfNonNull(property, MIN_LENGTH_KEY, range.lowerEndpoint());
638 putIfNonNull(property, MAX_LENGTH_KEY, range.upperEndpoint());
641 if (type.getPatternConstraints().iterator().hasNext()) {
642 final PatternConstraint pattern = type.getPatternConstraints().iterator().next();
643 String regex = pattern.getJavaPatternString();
644 regex = regex.substring(1, regex.length() - 1);
645 final Generex generex = new Generex(regex);
646 return generex.random();
648 return "Some " + nodeName;
652 private String processUnionType(final UnionTypeDefinition unionType, final ObjectNode property,
653 final SchemaContext schemaContext, final DataSchemaNode node) {
654 final ArrayNode unionNames = new ArrayNode(JsonNodeFactory.instance);
655 for (final TypeDefinition<?> typeDef : unionType.getTypes()) {
656 unionNames.add(processTypeDef(typeDef, node, property, schemaContext));
658 property.set(ENUM, unionNames);
659 return unionNames.iterator().next().asText();
663 * Helper method to generate a pre-filled JSON schema object.
665 private static ObjectNode getSchemaTemplate() {
666 final ObjectNode schemaJSON = JsonNodeFactory.instance.objectNode();
667 schemaJSON.put(SCHEMA_KEY, SCHEMA_URL);
672 private static void putIfNonNull(final ObjectNode property, final String key, final Number number) {
673 if (key != null && number != null) {
674 if (number instanceof Double) {
675 property.put(key, (Double) number);
676 } else if (number instanceof Float) {
677 property.put(key, (Float) number);
678 } else if (number instanceof Integer) {
679 property.put(key, (Integer) number);
680 } else if (number instanceof Short) {
681 property.put(key, (Short) number);
682 } else if (number instanceof Long) {
683 property.put(key, (Long) number);
688 private static void putIfNonNull(final ObjectNode property, final String key, final String value) {
689 if (key != null && value != null) {
690 property.put(key, value);