X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=restconf%2Fsal-rest-docgen%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fnetconf%2Fsal%2Frest%2Fdoc%2Fimpl%2FModelGenerator.java;h=b48754136baa95e24a8113569919c802553b6248;hb=d6cc6c95d317f2c678278feb41235de8316f128f;hp=63982faee6e41ff3acef67a2973a88800a54decb;hpb=f20c7a72ce5fede67598ff22548b0e43c409180d;p=netconf.git diff --git a/restconf/sal-rest-docgen/src/main/java/org/opendaylight/netconf/sal/rest/doc/impl/ModelGenerator.java b/restconf/sal-rest-docgen/src/main/java/org/opendaylight/netconf/sal/rest/doc/impl/ModelGenerator.java index 63982faee6..b48754136b 100644 --- a/restconf/sal-rest-docgen/src/main/java/org/opendaylight/netconf/sal/rest/doc/impl/ModelGenerator.java +++ b/restconf/sal-rest-docgen/src/main/java/org/opendaylight/netconf/sal/rest/doc/impl/ModelGenerator.java @@ -8,47 +8,60 @@ package org.opendaylight.netconf.sal.rest.doc.impl; import static org.opendaylight.netconf.sal.rest.doc.util.RestDocgenUtil.resolveNodesName; -import com.google.common.base.Preconditions; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; +import com.google.common.collect.Range; +import com.google.common.collect.RangeSet; +import com.mifmif.common.regex.Generex; import java.io.IOException; -import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.Set; +import java.util.regex.Pattern; import javax.annotation.concurrent.NotThreadSafe; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; import org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder; import org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.Post; import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.AnyDataSchemaNode; import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode; -import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode; -import org.opendaylight.yangtools.yang.model.api.ConstraintDefinition; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ElementCountConstraint; import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode; import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.MandatoryAware; import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath; import org.opendaylight.yangtools.yang.model.api.RpcDefinition; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaNode; import org.opendaylight.yangtools.yang.model.api.TypeDefinition; +import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode; import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition.Bit; import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition; -import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition.EnumPair; import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition; -import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint; +import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint; +import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint; +import org.opendaylight.yangtools.yang.model.api.type.RangeRestrictedTypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition; -import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition; +import org.opendaylight.yangtools.yang.model.util.RevisionAwareXPathImpl; +import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,10 +73,10 @@ public class ModelGenerator { private static final Logger LOG = LoggerFactory.getLogger(ModelGenerator.class); + private static final Pattern STRIP_PATTERN = Pattern.compile("\\[[^\\[\\]]*\\]"); private static final String BASE_64 = "base64"; private static final String BINARY_ENCODING_KEY = "binaryEncoding"; private static final String MEDIA_KEY = "media"; - private static final String ONE_OF_KEY = "oneOf"; private static final String UNIQUE_ITEMS_KEY = "uniqueItems"; private static final String MAX_ITEMS = "maxItems"; private static final String MIN_ITEMS = "minItems"; @@ -80,438 +93,572 @@ public class ModelGenerator { private static final String OBJECT_TYPE = "object"; private static final String ARRAY_TYPE = "array"; private static final String ENUM = "enum"; - private static final String INTEGER = "integer"; - private static final String NUMBER = "number"; - private static final String BOOLEAN = "boolean"; - private static final String STRING = "string"; private static final String ID_KEY = "id"; private static final String SUB_TYPES_KEY = "subTypes"; + private static final String UNIQUE_EMPTY_IDENTIFIER = "unique_empty_identifier"; private Module topLevelModule; public ModelGenerator() { } - private static String jsonTypeFor(final TypeDefinition type) { - if (type instanceof BooleanTypeDefinition) { - return BOOLEAN; - } else if (type instanceof DecimalTypeDefinition) { - return NUMBER; - } else if (type instanceof EnumTypeDefinition) { - return ENUM; - } else if (type instanceof IntegerTypeDefinition) { - return INTEGER; - } else if (type instanceof UnsignedIntegerTypeDefinition) { - return INTEGER; - } else if (type instanceof StringTypeDefinition) { - return STRING; - } - - // TODO: Binary type - return null; - } - - public JSONObject convertToJsonSchema(final Module module, final SchemaContext schemaContext) throws IOException, JSONException { - JSONObject models = new JSONObject(); + /** + * Creates Json models from provided module according to swagger spec. + * + * @param module - Yang module to be converted + * @param schemaContext - SchemaContext of all Yang files used by Api Doc + * @return ObjectNode containing data used for creating examples and models in Api Doc + * @throws IOException if I/O operation fails + */ + public ObjectNode convertToJsonSchema(final Module module, + final SchemaContext schemaContext) throws IOException { + final ObjectNode models = JsonNodeFactory.instance.objectNode(); + final ObjectNode emptyIdentifier = JsonNodeFactory.instance.objectNode(); + models.set(UNIQUE_EMPTY_IDENTIFIER, emptyIdentifier); topLevelModule = module; - processModules(module, models); + processModules(module, models, schemaContext); processContainersAndLists(module, models, schemaContext); processRPCs(module, models, schemaContext); processIdentities(module, models); return models; } - private void processModules(final Module module, final JSONObject models) throws JSONException { - createConcreteModelForPost(models, module.getName() + BaseYangSwaggerGenerator.MODULE_NAME_SUFFIX, createPropertiesForPost(module)); + private void processModules(final Module module, final ObjectNode models, + final SchemaContext schemaContext) { + createConcreteModelForPost(models, module.getName() + BaseYangSwaggerGenerator.MODULE_NAME_SUFFIX, + createPropertiesForPost(module, schemaContext, module.getName())); } - private void processContainersAndLists(final Module module, final JSONObject models, final SchemaContext schemaContext) - throws IOException, JSONException { + private void processContainersAndLists(final Module module, final ObjectNode models, + final SchemaContext schemaContext) throws IOException { + final String moduleName = module.getName(); - String moduleName = module.getName(); - - for (DataSchemaNode childNode : module.getChildNodes()) { + for (final DataSchemaNode childNode : module.getChildNodes()) { // For every container and list in the module if (childNode instanceof ContainerSchemaNode || childNode instanceof ListSchemaNode) { processDataNodeContainer((DataNodeContainer) childNode, moduleName, models, true, schemaContext); processDataNodeContainer((DataNodeContainer) childNode, moduleName, models, false, schemaContext); } } - } /** - * Process the RPCs for a Module Spits out a file each of the name -input.json and -output.json - * for each RPC that contains input & output elements + * Process the RPCs for a Module Spits out a file each of the name + * {@code -input.json and -output.json} + * for each RPC that contains input & output elements. * - * @param module - * @throws JSONException - * @throws IOException + * @param module module + * @throws IOException if I/O operation fails */ - private void processRPCs(final Module module, final JSONObject models, final SchemaContext schemaContext) throws JSONException, - IOException { - - Set rpcs = module.getRpcs(); - String moduleName = module.getName(); - for (RpcDefinition rpc : rpcs) { - - ContainerSchemaNode input = rpc.getInput(); - if (input != null) { - JSONObject inputJSON = processDataNodeContainer(input, moduleName, models, schemaContext); - String filename = "(" + rpc.getQName().getLocalName() + ")input"; - inputJSON.put("id", filename); - // writeToFile(filename, inputJSON.toString(2), moduleName); - models.put(filename, inputJSON); + private void processRPCs(final Module module, final ObjectNode models, + final SchemaContext schemaContext) throws IOException { + final Set rpcs = module.getRpcs(); + final String moduleName = module.getName(); + for (final RpcDefinition rpc : rpcs) { + final ContainerSchemaNode input = rpc.getInput(); + if (!input.getChildNodes().isEmpty()) { + final ObjectNode properties = + processChildren(input.getChildNodes(), moduleName, models, true, schemaContext); + + final String filename = "(" + rpc.getQName().getLocalName() + ")input"; + final ObjectNode childSchema = getSchemaTemplate(); + childSchema.put(TYPE_KEY, OBJECT_TYPE); + childSchema.set(PROPERTIES_KEY, properties); + childSchema.put(ID_KEY, filename); + models.set(filename, childSchema); + + processTopData(filename, models, input); } - ContainerSchemaNode output = rpc.getOutput(); - if (output != null) { - JSONObject outputJSON = processDataNodeContainer(output, moduleName, models, schemaContext); - String filename = "(" + rpc.getQName().getLocalName() + ")output"; - outputJSON.put("id", filename); - models.put(filename, outputJSON); + final ContainerSchemaNode output = rpc.getOutput(); + if (!output.getChildNodes().isEmpty()) { + final ObjectNode properties = + processChildren(output.getChildNodes(), moduleName, models, true, schemaContext); + final String filename = "(" + rpc.getQName().getLocalName() + ")output"; + final ObjectNode childSchema = getSchemaTemplate(); + childSchema.put(TYPE_KEY, OBJECT_TYPE); + childSchema.set(PROPERTIES_KEY, properties); + childSchema.put(ID_KEY, filename); + models.set(filename, childSchema); + + processTopData(filename, models, output); } } } + private ObjectNode processTopData(final String filename, final ObjectNode models, final SchemaNode schemaNode) { + final ObjectNode items = JsonNodeFactory.instance.objectNode(); + + items.put(REF_KEY, filename); + final ObjectNode dataNodeProperties = JsonNodeFactory.instance.objectNode(); + dataNodeProperties.put(TYPE_KEY, schemaNode instanceof ListSchemaNode ? ARRAY_TYPE : OBJECT_TYPE); + dataNodeProperties.set(ITEMS_KEY, items); + + putIfNonNull(dataNodeProperties, DESCRIPTION_KEY, schemaNode.getDescription().orElse(null)); + final ObjectNode properties = JsonNodeFactory.instance.objectNode(); + properties.set(topLevelModule.getName() + ":" + schemaNode.getQName().getLocalName(), dataNodeProperties); + final ObjectNode finalChildSchema = getSchemaTemplate(); + finalChildSchema.put(TYPE_KEY, OBJECT_TYPE); + finalChildSchema.set(PROPERTIES_KEY, properties); + finalChildSchema.put(ID_KEY, filename + OperationBuilder.TOP); + models.set(filename + OperationBuilder.TOP, finalChildSchema); + + return dataNodeProperties; + } + /** * Processes the 'identity' statement in a yang model and maps it to a 'model' in the Swagger JSON spec. * - * @param module - * The module from which the identity stmt will be processed - * @param models - * The JSONObject in which the parsed identity will be put as a 'model' obj + * @param module The module from which the identity stmt will be processed + * @param models The ObjectNode in which the parsed identity will be put as a 'model' obj */ - private static void processIdentities(final Module module, final JSONObject models) throws JSONException { + private static void processIdentities(final Module module, final ObjectNode models) { - String moduleName = module.getName(); - Set idNodes = module.getIdentities(); + final String moduleName = module.getName(); + final Set idNodes = module.getIdentities(); LOG.debug("Processing Identities for module {} . Found {} identity statements", moduleName, idNodes.size()); - for (IdentitySchemaNode idNode : idNodes) { - JSONObject identityObj = new JSONObject(); - String identityName = idNode.getQName().getLocalName(); + for (final IdentitySchemaNode idNode : idNodes) { + final ObjectNode identityObj = JsonNodeFactory.instance.objectNode(); + final String identityName = idNode.getQName().getLocalName(); LOG.debug("Processing Identity: {}", identityName); identityObj.put(ID_KEY, identityName); - identityObj.put(DESCRIPTION_KEY, idNode.getDescription()); + putIfNonNull(identityObj, DESCRIPTION_KEY, idNode.getDescription().orElse(null)); - JSONObject props = new JSONObject(); - IdentitySchemaNode baseId = idNode.getBaseIdentity(); + final ObjectNode props = JsonNodeFactory.instance.objectNode(); - if (baseId == null) { - /** + if (idNode.getBaseIdentities().isEmpty()) { + /* * This is a base identity. So lets see if it has sub types. If it does, then add them to the model * definition. */ - Set derivedIds = idNode.getDerivedIdentities(); + final Set derivedIds = idNode.getDerivedIdentities(); if (derivedIds != null) { - JSONArray subTypes = new JSONArray(); - for (IdentitySchemaNode derivedId : derivedIds) { - subTypes.put(derivedId.getQName().getLocalName()); + final ArrayNode subTypes = new ArrayNode(JsonNodeFactory.instance); + for (final IdentitySchemaNode derivedId : derivedIds) { + subTypes.add(derivedId.getQName().getLocalName()); } - identityObj.put(SUB_TYPES_KEY, subTypes); + identityObj.set(SUB_TYPES_KEY, subTypes); + } } else { - /** + /* * This is a derived entity. Add it's base type & move on. */ - props.put(TYPE_KEY, baseId.getQName().getLocalName()); + props.put(TYPE_KEY, idNode.getBaseIdentities().iterator().next().getQName().getLocalName()); } // Add the properties. For a base type, this will be an empty object as required by the Swagger spec. - identityObj.put(PROPERTIES_KEY, props); - models.put(identityName, identityObj); + identityObj.set(PROPERTIES_KEY, props); + models.set(identityName, identityObj); } } - /** - * Processes the container and list nodes and populates the moduleJSON. - */ - private JSONObject processDataNodeContainer(final DataNodeContainer dataNode, final String moduleName, final JSONObject models, - final SchemaContext schemaContext) throws JSONException, IOException { - return processDataNodeContainer(dataNode, moduleName, models, true, schemaContext); - } - - private JSONObject processDataNodeContainer(final DataNodeContainer dataNode, final String moduleName, final JSONObject models, - final boolean isConfig, final SchemaContext schemaContext) throws JSONException, IOException { + private ObjectNode processDataNodeContainer( + final DataNodeContainer dataNode, final String parentName, final ObjectNode models, final boolean isConfig, + final SchemaContext schemaContext) throws IOException { if (dataNode instanceof ListSchemaNode || dataNode instanceof ContainerSchemaNode) { - Preconditions.checkArgument(dataNode instanceof SchemaNode, "Data node should be also schema node"); - Iterable containerChildren = dataNode.getChildNodes(); - JSONObject properties = processChildren(containerChildren, ((SchemaNode) dataNode).getQName(), moduleName, - models, isConfig, schemaContext); - - String nodeName = (isConfig ? OperationBuilder.CONFIG : OperationBuilder.OPERATIONAL) - + ((SchemaNode) dataNode).getQName().getLocalName(); - - JSONObject childSchema = getSchemaTemplate(); + final Iterable containerChildren = dataNode.getChildNodes(); + final String localName = ((SchemaNode) dataNode).getQName().getLocalName(); + final ObjectNode properties = + processChildren(containerChildren, parentName + "/" + localName, models, isConfig, schemaContext); + final String nodeName = parentName + (isConfig ? OperationBuilder.CONFIG : OperationBuilder.OPERATIONAL) + + localName; + + final ObjectNode childSchema = getSchemaTemplate(); childSchema.put(TYPE_KEY, OBJECT_TYPE); - childSchema.put(PROPERTIES_KEY, properties); - childSchema.put("id", nodeName); - models.put(nodeName, childSchema); + childSchema.set(PROPERTIES_KEY, properties); + + childSchema.put(ID_KEY, nodeName); + models.set(nodeName, childSchema); if (isConfig) { - createConcreteModelForPost(models, ((SchemaNode) dataNode).getQName().getLocalName(), - createPropertiesForPost(dataNode)); + createConcreteModelForPost(models, localName, + createPropertiesForPost(dataNode, schemaContext, parentName + "/" + localName)); } - JSONObject items = new JSONObject(); - items.put(REF_KEY, nodeName); - JSONObject dataNodeProperties = new JSONObject(); - dataNodeProperties.put(TYPE_KEY, dataNode instanceof ListSchemaNode ? ARRAY_TYPE : OBJECT_TYPE); - dataNodeProperties.put(ITEMS_KEY, items); - - return dataNodeProperties; + return processTopData(nodeName, models, (SchemaNode) dataNode); } return null; } - private static void createConcreteModelForPost(final JSONObject models, final String localName, - final JSONObject properties) throws JSONException { - String nodePostName = OperationBuilder.CONFIG + localName + Post.METHOD_NAME; - JSONObject postSchema = getSchemaTemplate(); + private static void createConcreteModelForPost(final ObjectNode models, final String localName, + final JsonNode properties) { + final String nodePostName = OperationBuilder.CONFIG + localName + Post.METHOD_NAME; + final ObjectNode postSchema = getSchemaTemplate(); postSchema.put(TYPE_KEY, OBJECT_TYPE); - postSchema.put("id", nodePostName); - postSchema.put(PROPERTIES_KEY, properties); - models.put(nodePostName, postSchema); + postSchema.put(ID_KEY, nodePostName); + postSchema.set(PROPERTIES_KEY, properties); + models.set(nodePostName, postSchema); } - private JSONObject createPropertiesForPost(final DataNodeContainer dataNodeContainer) throws JSONException { - JSONObject properties = new JSONObject(); - for (DataSchemaNode childNode : dataNodeContainer.getChildNodes()) { + private JsonNode createPropertiesForPost(final DataNodeContainer dataNodeContainer, + final SchemaContext schemaContext, final String parentName) { + final ObjectNode properties = JsonNodeFactory.instance.objectNode(); + for (final DataSchemaNode childNode : dataNodeContainer.getChildNodes()) { if (childNode instanceof ListSchemaNode || childNode instanceof ContainerSchemaNode) { - JSONObject items = new JSONObject(); - items.put(REF_KEY, "(config)" + childNode.getQName().getLocalName()); - JSONObject property = new JSONObject(); + final ObjectNode items = JsonNodeFactory.instance.objectNode(); + items.put(REF_KEY, parentName + "(config)" + childNode.getQName().getLocalName()); + final ObjectNode property = JsonNodeFactory.instance.objectNode(); property.put(TYPE_KEY, childNode instanceof ListSchemaNode ? ARRAY_TYPE : OBJECT_TYPE); - property.put(ITEMS_KEY, items); - properties.put(childNode.getQName().getLocalName(), property); + property.set(ITEMS_KEY, items); + properties.set(childNode.getQName().getLocalName(), property); } else if (childNode instanceof LeafSchemaNode) { - JSONObject property = processLeafNode((LeafSchemaNode)childNode); - properties.put(childNode.getQName().getLocalName(), property); + final ObjectNode property = processLeafNode((LeafSchemaNode) childNode, schemaContext); + properties.set(childNode.getQName().getLocalName(), property); } } return properties; } - private JSONObject processChildren(final Iterable nodes, final QName parentQName, final String moduleName, - final JSONObject models, final SchemaContext schemaContext) throws JSONException, IOException { - return processChildren(nodes, parentQName, moduleName, models, true, schemaContext); - } - /** * Processes the nodes. */ - private JSONObject processChildren(final Iterable nodes, final QName parentQName, - final String moduleName, final JSONObject models, final boolean isConfig, final SchemaContext schemaContext) - throws JSONException, IOException { - - JSONObject properties = new JSONObject(); - - for (DataSchemaNode node : nodes) { + private ObjectNode processChildren( + final Iterable nodes, final String parentName, final ObjectNode models, + final boolean isConfig, final SchemaContext schemaContext) throws IOException { + final ObjectNode properties = JsonNodeFactory.instance.objectNode(); + for (final DataSchemaNode node : nodes) { if (node.isConfiguration() == isConfig) { - - String name = resolveNodesName(node, topLevelModule, schemaContext); - JSONObject property = null; + final String name = resolveNodesName(node, topLevelModule, schemaContext); + final ObjectNode property; if (node instanceof LeafSchemaNode) { - property = processLeafNode((LeafSchemaNode) node); + property = processLeafNode((LeafSchemaNode) node, schemaContext); + } else if (node instanceof ListSchemaNode) { - property = processDataNodeContainer((ListSchemaNode) node, moduleName, models, isConfig, + property = processDataNodeContainer((ListSchemaNode) node, parentName, models, isConfig, schemaContext); } else if (node instanceof LeafListSchemaNode) { - property = processLeafListNode((LeafListSchemaNode) node); + property = processLeafListNode((LeafListSchemaNode) node, schemaContext); } else if (node instanceof ChoiceSchemaNode) { - property = processChoiceNode((ChoiceSchemaNode) node, moduleName, models, schemaContext); + if (((ChoiceSchemaNode) node).getCases().values().iterator().hasNext()) { + processChoiceNode(((ChoiceSchemaNode) node).getCases().values().iterator().next() + .getChildNodes(), parentName, models, schemaContext, isConfig, properties); + } + continue; } else if (node instanceof AnyXmlSchemaNode) { property = processAnyXMLNode((AnyXmlSchemaNode) node); + } else if (node instanceof AnyDataSchemaNode) { + property = processAnydataNode((AnyDataSchemaNode) node); + } else if (node instanceof ContainerSchemaNode) { - property = processDataNodeContainer((ContainerSchemaNode) node, moduleName, models, isConfig, + property = processDataNodeContainer((ContainerSchemaNode) node, parentName, models, isConfig, schemaContext); } else { throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass()); } - - property.putOpt(DESCRIPTION_KEY, node.getDescription()); - properties.put(name, property); + putIfNonNull(property, DESCRIPTION_KEY, node.getDescription().orElse(null)); + properties.set(topLevelModule.getName() + ":" + name, property); } } return properties; } - private JSONObject processLeafListNode(final LeafListSchemaNode listNode) throws JSONException { - JSONObject props = new JSONObject(); + private ObjectNode processLeafListNode(final LeafListSchemaNode listNode, + final SchemaContext schemaContext) { + final ObjectNode props = JsonNodeFactory.instance.objectNode(); props.put(TYPE_KEY, ARRAY_TYPE); - JSONObject itemsVal = new JSONObject(); - processTypeDef(listNode.getType(), itemsVal); - props.put(ITEMS_KEY, itemsVal); + final ObjectNode itemsVal = JsonNodeFactory.instance.objectNode(); + final Optional optConstraint = listNode.getElementCountConstraint(); + final int max; + if (optConstraint.isPresent()) { + final Integer constraintMax = optConstraint.get().getMaxElements(); + max = constraintMax == null ? 2 : constraintMax; + processElementCount(optConstraint.get(), props); + } else { + max = 2; + } - ConstraintDefinition constraints = listNode.getConstraints(); - processConstraints(constraints, props); + if (max >= 2) { + processTypeDef(listNode.getType(), listNode, itemsVal, schemaContext); + processTypeDef(listNode.getType(), listNode, itemsVal, schemaContext); + } else { + processTypeDef(listNode.getType(), listNode, itemsVal, schemaContext); + } + props.set(ITEMS_KEY, itemsVal); return props; } - private JSONObject processChoiceNode(final ChoiceSchemaNode choiceNode, final String moduleName, final JSONObject models, - final SchemaContext schemaContext) throws JSONException, IOException { + private void processChoiceNode( + final Iterable nodes, final String moduleName, final ObjectNode models, + final SchemaContext schemaContext, final boolean isConfig, final ObjectNode properties) + throws IOException { + for (final DataSchemaNode node : nodes) { + final String name = resolveNodesName(node, topLevelModule, schemaContext); + final ObjectNode property; - Set cases = choiceNode.getCases(); + if (node instanceof LeafSchemaNode) { + property = processLeafNode((LeafSchemaNode) node, schemaContext); - JSONArray choiceProps = new JSONArray(); - for (ChoiceCaseNode choiceCase : cases) { - String choiceName = choiceCase.getQName().getLocalName(); - JSONObject choiceProp = processChildren(choiceCase.getChildNodes(), choiceCase.getQName(), moduleName, - models, schemaContext); - JSONObject choiceObj = new JSONObject(); - choiceObj.put(choiceName, choiceProp); - choiceObj.put(TYPE_KEY, OBJECT_TYPE); - choiceProps.put(choiceObj); - } + } else if (node instanceof ListSchemaNode) { + property = processDataNodeContainer((ListSchemaNode) node, moduleName, models, isConfig, + schemaContext); - JSONObject oneOfProps = new JSONObject(); - oneOfProps.put(ONE_OF_KEY, choiceProps); - oneOfProps.put(TYPE_KEY, OBJECT_TYPE); + } else if (node instanceof LeafListSchemaNode) { + property = processLeafListNode((LeafListSchemaNode) node, schemaContext); - return oneOfProps; - } + } else if (node instanceof ChoiceSchemaNode) { + if (((ChoiceSchemaNode) node).getCases().values().iterator().hasNext()) { + processChoiceNode(((ChoiceSchemaNode) node).getCases().values().iterator().next().getChildNodes(), + moduleName, models, schemaContext, isConfig, properties); + } + continue; + + } else if (node instanceof AnyXmlSchemaNode) { + property = processAnyXMLNode((AnyXmlSchemaNode) node); - private static void processConstraints(final ConstraintDefinition constraints, final JSONObject props) throws JSONException { - boolean isMandatory = constraints.isMandatory(); - props.put(REQUIRED_KEY, isMandatory); + } else if (node instanceof AnyDataSchemaNode) { + property = processAnydataNode((AnyDataSchemaNode) node); - Integer minElements = constraints.getMinElements(); - Integer maxElements = constraints.getMaxElements(); + } else if (node instanceof ContainerSchemaNode) { + property = processDataNodeContainer((ContainerSchemaNode) node, moduleName, models, isConfig, + schemaContext); + + } else { + throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass()); + } + + putIfNonNull(property, DESCRIPTION_KEY, node.getDescription().orElse(null)); + properties.set(name, property); + } + } + + private static void processElementCount(final ElementCountConstraint constraint, final ObjectNode props) { + final Integer minElements = constraint.getMinElements(); if (minElements != null) { props.put(MIN_ITEMS, minElements); } + final Integer maxElements = constraint.getMaxElements(); if (maxElements != null) { props.put(MAX_ITEMS, maxElements); } } - private JSONObject processLeafNode(final LeafSchemaNode leafNode) throws JSONException { - JSONObject property = new JSONObject(); + private static void processMandatory(final MandatoryAware node, final ObjectNode props) { + props.put(REQUIRED_KEY, node.isMandatory()); + } - String leafDescription = leafNode.getDescription(); - property.put(DESCRIPTION_KEY, leafDescription); + private ObjectNode processLeafNode(final LeafSchemaNode leafNode, + final SchemaContext schemaContext) { + final ObjectNode property = JsonNodeFactory.instance.objectNode(); - processConstraints(leafNode.getConstraints(), property); - processTypeDef(leafNode.getType(), property); + final String leafDescription = leafNode.getDescription().orElse(null); + putIfNonNull(property, DESCRIPTION_KEY, leafDescription); + processMandatory(leafNode, property); + processTypeDef(leafNode.getType(), leafNode, property, schemaContext); return property; } - private static JSONObject processAnyXMLNode(final AnyXmlSchemaNode leafNode) throws JSONException { - JSONObject property = new JSONObject(); + private static ObjectNode processAnydataNode(final AnyDataSchemaNode leafNode) { + final ObjectNode property = JsonNodeFactory.instance.objectNode(); - String leafDescription = leafNode.getDescription(); - property.put(DESCRIPTION_KEY, leafDescription); + final String leafDescription = leafNode.getDescription().orElse(null); + putIfNonNull(property, DESCRIPTION_KEY, leafDescription); - processConstraints(leafNode.getConstraints(), property); + processMandatory(leafNode, property); + final String localName = leafNode.getQName().getLocalName(); + property.put(TYPE_KEY, "example of anydata " + localName); return property; } - private void processTypeDef(final TypeDefinition leafTypeDef, final JSONObject property) throws JSONException { - if (leafTypeDef instanceof BinaryTypeDefinition) { - processBinaryType((BinaryTypeDefinition) leafTypeDef, property); - } else if (leafTypeDef instanceof BitsTypeDefinition) { - processBitsType((BitsTypeDefinition) leafTypeDef, property); - } else if (leafTypeDef instanceof EnumTypeDefinition) { - processEnumType((EnumTypeDefinition) leafTypeDef, property); - } else if (leafTypeDef instanceof IdentityrefTypeDefinition) { - property.putOpt(TYPE_KEY, - ((IdentityrefTypeDefinition) leafTypeDef).getIdentity().getQName().getLocalName()); - } else if (leafTypeDef instanceof StringTypeDefinition) { - processStringType((StringTypeDefinition) leafTypeDef, property); - } else if (leafTypeDef instanceof UnionTypeDefinition) { - processUnionType((UnionTypeDefinition) leafTypeDef, property); - } else { - String jsonType = jsonTypeFor(leafTypeDef); - if (jsonType == null) { - jsonType = "object"; + private static ObjectNode processAnyXMLNode(final AnyXmlSchemaNode leafNode) { + final ObjectNode property = JsonNodeFactory.instance.objectNode(); + + final String leafDescription = leafNode.getDescription().orElse(null); + putIfNonNull(property, DESCRIPTION_KEY, leafDescription); + + processMandatory(leafNode, property); + final String localName = leafNode.getQName().getLocalName(); + property.put(TYPE_KEY, "example of anyxml " + localName); + + return property; + } + + private String processTypeDef(final TypeDefinition leafTypeDef, final DataSchemaNode node, + final ObjectNode property, final SchemaContext schemaContext) { + final String jsonType; + if (leafTypeDef.getDefaultValue() == null) { + if (leafTypeDef instanceof BinaryTypeDefinition) { + jsonType = processBinaryType(property); + + } else if (leafTypeDef instanceof BitsTypeDefinition) { + jsonType = processBitsType((BitsTypeDefinition) leafTypeDef, property); + + } else if (leafTypeDef instanceof EnumTypeDefinition) { + jsonType = processEnumType((EnumTypeDefinition) leafTypeDef, property); + + } else if (leafTypeDef instanceof IdentityrefTypeDefinition) { + final String name = topLevelModule.getName(); + jsonType = name + ":" + ((IdentityrefTypeDefinition) leafTypeDef).getIdentities().iterator().next() + .getQName().getLocalName(); + + } else if (leafTypeDef instanceof StringTypeDefinition) { + jsonType = processStringType(leafTypeDef, property, node.getQName().getLocalName()); + + } else if (leafTypeDef instanceof UnionTypeDefinition) { + jsonType = processUnionType((UnionTypeDefinition) leafTypeDef, property, schemaContext, node); + + } else if (leafTypeDef instanceof EmptyTypeDefinition) { + jsonType = UNIQUE_EMPTY_IDENTIFIER; + + } else if (leafTypeDef instanceof LeafrefTypeDefinition) { + return processLeafRef(node, property, schemaContext, leafTypeDef); + + } else if (leafTypeDef instanceof BooleanTypeDefinition) { + jsonType = "true"; + + } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) { + final Number maybeLower = ((RangeRestrictedTypeDefinition) leafTypeDef).getRangeConstraint() + .map(RangeConstraint::getAllowedRanges).map(RangeSet::span).map(Range::lowerEndpoint) + .orElse(null); + jsonType = String.valueOf(maybeLower); + + } else { + jsonType = OBJECT_TYPE; + } - property.putOpt(TYPE_KEY, jsonType); + } else { + jsonType = String.valueOf(leafTypeDef.getDefaultValue()); } + putIfNonNull(property, TYPE_KEY, jsonType); + return jsonType; } - private static void processBinaryType(final BinaryTypeDefinition binaryType, final JSONObject property) throws JSONException { - property.put(TYPE_KEY, STRING); - JSONObject media = new JSONObject(); + private String processLeafRef(final DataSchemaNode node, final ObjectNode property, + final SchemaContext schemaContext, final TypeDefinition leafTypeDef) { + RevisionAwareXPath xpath = ((LeafrefTypeDefinition) leafTypeDef).getPathStatement(); + final SchemaNode schemaNode; + + final String xPathString = STRIP_PATTERN.matcher(xpath.toString()).replaceAll(""); + xpath = new RevisionAwareXPathImpl(xPathString, xpath.isAbsolute()); + + final Module module; + if (xpath.isAbsolute()) { + module = findModule(schemaContext, leafTypeDef.getQName()); + schemaNode = SchemaContextUtil.findDataSchemaNode(schemaContext, module, xpath); + } else { + module = findModule(schemaContext, node.getQName()); + schemaNode = SchemaContextUtil.findDataSchemaNodeForRelativeXPath(schemaContext, module, node, xpath); + } + + return processTypeDef(((TypedDataSchemaNode) schemaNode).getType(), (DataSchemaNode) schemaNode, + property, schemaContext); + } + + private static Module findModule(final SchemaContext schemaContext, final QName qualifiedName) { + return schemaContext.findModule(qualifiedName.getNamespace(), qualifiedName.getRevision()).orElse(null); + } + + private static String processBinaryType(final ObjectNode property) { + final ObjectNode media = JsonNodeFactory.instance.objectNode(); media.put(BINARY_ENCODING_KEY, BASE_64); - property.put(MEDIA_KEY, media); + property.set(MEDIA_KEY, media); + return "bin1 bin2"; } - private static void processEnumType(final EnumTypeDefinition enumLeafType, final JSONObject property) throws JSONException { - List enumPairs = enumLeafType.getValues(); - List enumNames = new ArrayList<>(); - for (EnumPair enumPair : enumPairs) { - enumNames.add(enumPair.getName()); + private static String processEnumType(final EnumTypeDefinition enumLeafType, + final ObjectNode property) { + final List enumPairs = enumLeafType.getValues(); + ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance); + for (final EnumPair enumPair : enumPairs) { + enumNames.add(new TextNode(enumPair.getName())); } - property.putOpt(ENUM, new JSONArray(enumNames)); + + property.set(ENUM, enumNames); + return enumLeafType.getValues().iterator().next().getName(); } - private static void processBitsType(final BitsTypeDefinition bitsType, final JSONObject property) throws JSONException { - property.put(TYPE_KEY, ARRAY_TYPE); + private static String processBitsType(final BitsTypeDefinition bitsType, + final ObjectNode property) { property.put(MIN_ITEMS, 0); property.put(UNIQUE_ITEMS_KEY, true); - JSONArray enumValues = new JSONArray(); - - List bits = bitsType.getBits(); - for (Bit bit : bits) { - enumValues.put(bit.getName()); + ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance); + final List bits = bitsType.getBits(); + for (final Bit bit : bits) { + enumNames.add(new TextNode(bit.getName())); } - JSONObject itemsValue = new JSONObject(); - itemsValue.put(ENUM, enumValues); - property.put(ITEMS_KEY, itemsValue); + property.set(ENUM, enumNames); + + return enumNames.iterator().next() + " " + enumNames.get(enumNames.size() - 1); } - private static void processStringType(final StringTypeDefinition stringType, final JSONObject property) throws JSONException { - StringTypeDefinition type = stringType; - List lengthConstraints = stringType.getLengthConstraints(); - while (lengthConstraints.isEmpty() && type.getBaseType() != null) { + private static String processStringType(final TypeDefinition stringType, + final ObjectNode property, final String nodeName) { + StringTypeDefinition type = (StringTypeDefinition) stringType; + Optional lengthConstraints = ((StringTypeDefinition) stringType).getLengthConstraint(); + while (!lengthConstraints.isPresent() && type.getBaseType() != null) { type = type.getBaseType(); - lengthConstraints = type.getLengthConstraints(); + lengthConstraints = type.getLengthConstraint(); } - // FIXME: json-schema is not expressive enough to capture min/max laternatives. We should find the true minimum - // and true maximum implied by the constraints and use that. - for (LengthConstraint lengthConstraint : lengthConstraints) { - Number min = lengthConstraint.getMin(); - Number max = lengthConstraint.getMax(); - property.putOpt(MIN_LENGTH_KEY, min); - property.putOpt(MAX_LENGTH_KEY, max); + if (lengthConstraints.isPresent()) { + final Range range = lengthConstraints.get().getAllowedRanges().span(); + putIfNonNull(property, MIN_LENGTH_KEY, range.lowerEndpoint()); + putIfNonNull(property, MAX_LENGTH_KEY, range.upperEndpoint()); } - property.put(TYPE_KEY, STRING); + if (type.getPatternConstraints().iterator().hasNext()) { + final PatternConstraint pattern = type.getPatternConstraints().iterator().next(); + String regex = pattern.getJavaPatternString(); + regex = regex.substring(1, regex.length() - 1); + final Generex generex = new Generex(regex); + return generex.random(); + } else { + return "Some " + nodeName; + } } - private static void processUnionType(final UnionTypeDefinition unionType, final JSONObject property) throws JSONException { - StringBuilder type = new StringBuilder(); - for (TypeDefinition typeDef : unionType.getTypes()) { - if (type.length() > 0) { - type.append(" or "); - } - type.append(jsonTypeFor(typeDef)); + private String processUnionType(final UnionTypeDefinition unionType, final ObjectNode property, + final SchemaContext schemaContext, final DataSchemaNode node) { + final ArrayNode unionNames = new ArrayNode(JsonNodeFactory.instance); + for (final TypeDefinition typeDef : unionType.getTypes()) { + unionNames.add(processTypeDef(typeDef, node, property, schemaContext)); } - - property.put(TYPE_KEY, type); + property.set(ENUM, unionNames); + return unionNames.iterator().next().asText(); } /** * Helper method to generate a pre-filled JSON schema object. */ - private static JSONObject getSchemaTemplate() throws JSONException { - JSONObject schemaJSON = new JSONObject(); + private static ObjectNode getSchemaTemplate() { + final ObjectNode schemaJSON = JsonNodeFactory.instance.objectNode(); schemaJSON.put(SCHEMA_KEY, SCHEMA_URL); return schemaJSON; } -} + private static void putIfNonNull(final ObjectNode property, final String key, final Number number) { + if (key != null && number != null) { + if (number instanceof Double) { + property.put(key, (Double) number); + } else if (number instanceof Float) { + property.put(key, (Float) number); + } else if (number instanceof Integer) { + property.put(key, (Integer) number); + } else if (number instanceof Short) { + property.put(key, (Short) number); + } else if (number instanceof Long) { + property.put(key, (Long) number); + } + } + } + + private static void putIfNonNull(final ObjectNode property, final String key, final String value) { + if (key != null && value != null) { + property.put(key, value); + } + } + +} \ No newline at end of file