/* * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.yangtools.yang.unified.doc.generator import org.opendaylight.yangtools.yang.model.api.SchemaContext import java.io.File import java.util.Set import org.opendaylight.yangtools.yang.model.api.Module import java.io.IOException import java.util.HashSet import java.io.BufferedWriter import java.io.OutputStreamWriter; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode import org.opendaylight.yangtools.yang.model.api.ListSchemaNode import org.opendaylight.yangtools.yang.model.api.TypeDefinition import org.opendaylight.yangtools.yang.model.api.SchemaNode import org.opendaylight.yangtools.yang.model.util.ExtendedType import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition import java.text.SimpleDateFormat import java.util.Collection import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition import org.opendaylight.yangtools.yang.model.api.NotificationDefinition import org.opendaylight.yangtools.yang.model.api.DataNodeContainer import org.slf4j.LoggerFactory import org.slf4j.Logger import java.util.List import org.opendaylight.yangtools.yang.common.QName import org.opendaylight.yangtools.yang.model.api.RpcDefinition import org.opendaylight.yangtools.yang.model.api.ExtensionDefinition import java.util.ArrayList import java.util.Map import org.opendaylight.yangtools.yang.model.api.SchemaPath import org.sonatype.plexus.build.incremental.BuildContext; import org.sonatype.plexus.build.incremental.DefaultBuildContext; import org.opendaylight.yangtools.yang.model.api.ChoiceNode import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates import java.util.LinkedHashMap import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode import java.util.HashMap import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode class GeneratorImpl { File path static val REVISION_FORMAT = new SimpleDateFormat("yyyy-MM-dd") static val Logger LOG = LoggerFactory.getLogger(GeneratorImpl) static val BuildContext CTX = new DefaultBuildContext(); var Module currentModule; def generate(SchemaContext context, File targetPath, Set modulesToGen) throws IOException { path = targetPath; path.mkdirs(); val it = new HashSet; for (module : modulesToGen) { add(generateDocumentation(module, context)); } return it; } def generateDocumentation(Module module, SchemaContext ctx) { val destination = new File(path, '''«module.name».html''') try { val fw = new OutputStreamWriter(CTX.newFileOutputStream(destination)) val bw = new BufferedWriter(fw) currentModule = module; bw.append(generate(module, ctx)); bw.close(); fw.close(); } catch (IOException e) { LOG.error(e.getMessage()); } return destination; } def generate(Module module, SchemaContext ctx) ''' «module.name» «body(module, ctx)» ''' def body(Module module, SchemaContext ctx) ''' «header(module)» «typeDefinitions(module)» «identities(module)» «groupings(module)» «dataStore(module)» «childNodes(module)» «notifications(module)» «augmentations(module, ctx)» «rpcs(module)» «extensions(module)» «features(module)» ''' def typeDefinitions(Module module) { val Set> typedefs = module.typeDefinitions if (typedefs.empty) { return ''; } return '''

Type Definitions

''' } private def identities(Module module) { if (module.identities.empty) { return ''; } return '''

Identities

''' } private def groupings(Module module) { if (module.groupings.empty) { return ''; } return '''

Groupings

''' } def dataStore(Module module) { if (module.childNodes.empty) { return ''; } return '''

Datastore Structure

«tree(module)» ''' } def augmentations(Module module, SchemaContext context) { if (module.augmentations.empty) { return ''; } return '''

Augmentations

''' } def notifications(Module module) { val Set notificationdefs = module.notifications if (notificationdefs.empty) { return ''; } return '''

Notifications

«FOR notificationdef : notificationdefs»

«notificationdef.nodeName»

«notificationdef.descAndRef» «notificationdef.childNodes.printChildren(3,InstanceIdentifier.builder().toInstance())» «ENDFOR» ''' } def rpcs(Module module) { if (module.rpcs.empty) { return ''; } return '''

RPC Definitions

«FOR rpc : module.rpcs»

«rpc.nodeName»

«rpc.rpcInfo(InstanceIdentifier.builder().node(rpc.QName).toInstance())» «ENDFOR» ''' } def extensions(Module module) { if (module.extensionSchemaNodes.empty) { return ''; } return '''

Extensions

«FOR ext : module.extensionSchemaNodes»
  • «ext.nodeName»

  • «extensionInfo(ext)» «ENDFOR» ''' } def features(Module module) { if (module.features.empty) { return ''; } return '''

    Features

      «FOR feature : module.features»
    • «strong("feature " + feature.QName.localName)»
        «feature.descAndRefLi»
    • «ENDFOR»
    ''' } def header(Module module) '''

    «module.name»

    Base Information

    Prefix
    «pre(module.prefix)»
    Namespace
    «pre(module.namespace.toString)»
    Revision
    «pre(REVISION_FORMAT.format(module.revision))»
    «FOR imp : module.imports BEFORE "
    Imports
    " »
    «code(imp.prefix)» = «code(imp.moduleName)»
    «ENDFOR»
    ''' def code(String string) '''«string»''' def process(Module module) { throw new UnsupportedOperationException("TODO: auto-generated method stub") } def CharSequence tree(Module module) ''' «strong("module " + module.name)» «module.childNodes.treeSet(InstanceIdentifier.builder.toInstance())» ''' private def dispatch CharSequence tree(ChoiceNode node,InstanceIdentifier path) ''' «node.nodeName» (choice) «casesTree(node.cases,path)» ''' def casesTree(Set nodes,InstanceIdentifier path) '''
      «FOR node : nodes»
    • «node.nodeName» «node.childNodes.treeSet(path)»
    • «ENDFOR»
    ''' private def dispatch CharSequence tree(DataSchemaNode node,InstanceIdentifier path) ''' «node.nodeName» ''' private def dispatch CharSequence tree(ListSchemaNode node,InstanceIdentifier path) ''' «val newPath = path.append(node)» «localLink(newPath,node.nodeName)» «node.childNodes.treeSet(newPath)» ''' private def dispatch CharSequence tree(ContainerSchemaNode node,InstanceIdentifier path) ''' «val newPath = path.append(node)» «localLink(newPath,node.nodeName)» «node.childNodes.treeSet(newPath)» ''' def CharSequence childNodes(Module module) ''' «val childNodes = module.childNodes» «IF childNodes !== null && !childNodes.empty»

    Child nodes

    «childNodes.printChildren(3,InstanceIdentifier.builder().toInstance())» «ENDIF» ''' def CharSequence printChildren(Set nodes, int level, InstanceIdentifier path) { val anyxmlNodes = nodes.filter(AnyXmlSchemaNode) val leafNodes = nodes.filter(LeafSchemaNode) val leafListNodes = nodes.filter(LeafListSchemaNode) val choices = nodes.filter(ChoiceNode) val cases = nodes.filter(ChoiceCaseNode) val containers = nodes.filter(ContainerSchemaNode) val lists = nodes.filter(ListSchemaNode) return ''' «IF ((anyxmlNodes.size + leafNodes.size + leafListNodes.size + containers.size + lists.size) > 0)»

    Direct children

      «FOR childNode : anyxmlNodes» «childNode.printShortInfo(level,path)» «ENDFOR» «FOR childNode : leafNodes» «childNode.printShortInfo(level,path)» «ENDFOR» «FOR childNode : leafListNodes» «childNode.printShortInfo(level,path)» «ENDFOR» «FOR childNode : containers» «childNode.printShortInfo(level,path)» «ENDFOR» «FOR childNode : lists» «childNode.printShortInfo(level,path)» «ENDFOR»
    «ENDIF» «IF !path.path.empty»

    XML example

    «nodes.xmlExample(path.path.last.nodeType,path)» «ENDIF» «FOR childNode : containers» «childNode.printInfo(level,path)» «ENDFOR» «FOR childNode : lists» «childNode.printInfo(level,path)» «ENDFOR» «FOR childNode : choices» «childNode.printInfo(level,path)» «ENDFOR» «FOR childNode : cases» «childNode.printInfo(level,path)» «ENDFOR» ''' } def CharSequence xmlExample(Set nodes, QName name,InstanceIdentifier path) '''
            «xmlExampleTag(name,nodes.xmplExampleTags(path))»
        
    ''' def CharSequence xmplExampleTags(Set nodes, InstanceIdentifier identifier) ''' «FOR node : nodes» «node.asXmlExampleTag(identifier)» «ENDFOR» ''' private def dispatch CharSequence asXmlExampleTag(LeafSchemaNode node, InstanceIdentifier identifier) ''' «node.QName.xmlExampleTag("...")» ''' private def dispatch CharSequence asXmlExampleTag(LeafListSchemaNode node, InstanceIdentifier identifier) ''' <!-- This node could appear multiple times --> «node.QName.xmlExampleTag("...")» ''' private def dispatch CharSequence asXmlExampleTag(ContainerSchemaNode node, InstanceIdentifier identifier) ''' <!-- See «localLink(identifier.append(node),"definition")» for child nodes. --> «node.QName.xmlExampleTag("...")» ''' private def dispatch CharSequence asXmlExampleTag(ListSchemaNode node, InstanceIdentifier identifier) ''' <!-- See «localLink(identifier.append(node),"definition")» for child nodes. --> <!-- This node could appear multiple times --> «node.QName.xmlExampleTag("...")» ''' private def dispatch CharSequence asXmlExampleTag(DataSchemaNode node, InstanceIdentifier identifier) ''' ''' def xmlExampleTag(QName name, CharSequence data) { return '''<«name.localName» xmlns="«name.namespace»">«data»</«name.localName»>''' } def header(int level,QName name) '''«name.localName»''' def header(int level,InstanceIdentifier name) ''' «FOR cmp : name.path SEPARATOR "/"»«cmp.nodeType.localName»«ENDFOR» ''' private def dispatch CharSequence printInfo(DataSchemaNode node, int level, InstanceIdentifier path) ''' «header(level+1,node.QName)» ''' private def dispatch CharSequence printInfo(ContainerSchemaNode node, int level, InstanceIdentifier path) ''' «val newPath = path.append(node)» «header(level,newPath)»
    XML Path
    «newPath.asXmlPath»
    Restconf path
    «code(newPath.asRestconfPath)»
    «node.childNodes.printChildren(level,newPath)» ''' private def dispatch CharSequence printInfo(ListSchemaNode node, int level, InstanceIdentifier path) ''' «val newPath = path.append(node)» «header(level,newPath)»
    XML Path
    «newPath.asXmlPath»
    Restconf path
    «code(newPath.asRestconfPath)»
    «node.childNodes.printChildren(level,newPath)» ''' private def dispatch CharSequence printInfo(ChoiceNode node, int level, InstanceIdentifier path) ''' «val Set choiceCases = new HashSet(node.cases)» «choiceCases.printChildren(level,path)» ''' private def dispatch CharSequence printInfo(ChoiceCaseNode node, int level, InstanceIdentifier path) ''' «node.childNodes.printChildren(level,path)» ''' def CharSequence printShortInfo(ContainerSchemaNode node, int level, InstanceIdentifier path) { val newPath = path.append(node); return '''
  • «strong(localLink(newPath,node.QName.localName))» (container)
  • ''' } def CharSequence printShortInfo(ListSchemaNode node, int level, InstanceIdentifier path) { val newPath = path.append(node); return '''
  • «strong(localLink(newPath,node.QName.localName))» (list)
  • ''' } def CharSequence printShortInfo(AnyXmlSchemaNode node, int level, InstanceIdentifier path) { return '''
  • «strong((node.QName.localName))» (anyxml)
  • ''' } def CharSequence printShortInfo(LeafSchemaNode node, int level, InstanceIdentifier path) { return '''
  • «strong((node.QName.localName))» (leaf)
  • ''' } def CharSequence printShortInfo(LeafListSchemaNode node, int level, InstanceIdentifier path) { return '''
  • «strong((node.QName.localName))» (leaf-list)
  • ''' } def CharSequence localLink(InstanceIdentifier identifier, CharSequence text) ''' «text» ''' private def dispatch InstanceIdentifier append(InstanceIdentifier identifier, ContainerSchemaNode node) { val pathArguments = new ArrayList(identifier.path) pathArguments.add(new NodeIdentifier(node.QName)); return new InstanceIdentifier(pathArguments); } private def dispatch InstanceIdentifier append(InstanceIdentifier identifier, ListSchemaNode node) { val pathArguments = new ArrayList(identifier.path) val keyValues = new LinkedHashMap(); if(node.keyDefinition != null) { for(definition : node.keyDefinition) { keyValues.put(definition,new Object); } } pathArguments.add(new NodeIdentifierWithPredicates(node.QName,keyValues)); return new InstanceIdentifier(pathArguments); } def asXmlPath(InstanceIdentifier identifier) { return ""; } def asRestconfPath(InstanceIdentifier identifier) { val it = new StringBuilder(); append(currentModule.name) append(":") var previous = false; for(arg : identifier.path) { if(previous) append("/") append(arg.nodeType.localName); previous = true; if(arg instanceof NodeIdentifierWithPredicates) { val nodeIdentifier = arg as NodeIdentifierWithPredicates; for(qname : nodeIdentifier.keyValues.keySet) { append("/{"); append(qname.localName) append("}") } } } return it.toString; } private def String schemaPathAsRestconfPath(Module module, SchemaPath schemaPath, SchemaContext ctx) { val Map imports = new HashMap(); for (mImport : module.imports) { imports.put(mImport.prefix, mImport.moduleName) } val List path = schemaPath.path val StringBuilder pathString = new StringBuilder() if (schemaPath.absolute) { pathString.append("/") } val QName qname = path.get(0) var Object parent = ctx.findModuleByNamespaceAndRevision(qname.namespace, qname.revision) for (name : path) { if (parent instanceof DataNodeContainer) { var SchemaNode node = (parent as DataNodeContainer).getDataChildByName(name) if (node == null && (parent instanceof Module)) { val notifications = (parent as Module).notifications; for (notification : notifications) { if (notification.QName.equals(name)) { node = notification } } } if (node == null && (parent instanceof Module)) { val rpcs = (parent as Module).rpcs; for (rpc : rpcs) { if (rpc.QName.equals(name)) { node = rpc } } } if (!(node instanceof ChoiceNode) && !(node instanceof ChoiceCaseNode)) { var String prefix = name.prefix var String moduleName if (prefix == null || "".equals(prefix) || prefix.equals(module.prefix)) { moduleName = module.name } else { moduleName = imports.get(prefix) } pathString.append(moduleName) pathString.append(":") pathString.append(name.localName) pathString.append("/") } parent = node } else if (parent instanceof ChoiceNode) { parent = (parent as ChoiceNode).getCaseNodeByName(qname.localName) } } return pathString.toString; } def CharSequence childNodesInfoTree(Map childNodes) ''' «IF childNodes !== null && !childNodes.empty» «FOR child : childNodes.values» «childInfo(child, childNodes)» «ENDFOR» «ENDIF» ''' def CharSequence childInfo(DataSchemaNode node, Map childNodes) ''' «val String path = nodeSchemaPathToPath(node, childNodes)» «IF path != null» «code(path)» «IF node !== null»
      «node.descAndRefLi»
    «ENDIF» «ENDIF» ''' private def CharSequence treeSet(Collection childNodes, InstanceIdentifier path) ''' «IF childNodes !== null && !childNodes.empty»
      «FOR child : childNodes»
    • «child.tree(path)»
    • «ENDFOR»
    «ENDIF» ''' def listKeys(ListSchemaNode node) ''' [«FOR key : node.keyDefinition SEPARATOR " "»«key.localName»«ENDFOR»] ''' private def CharSequence rpcInfo(RpcDefinition rpc,InstanceIdentifier path) '''
      «rpc.descAndRefLi»
    • «rpc.input.tree(path)»
    • «rpc.output.tree(path)»
    ''' private def CharSequence extensionInfo(ExtensionDefinition ext) '''
      «ext.descAndRefLi» «listItem("Argument", ext.argument)»
    ''' private def dispatch CharSequence tree(Void obj, InstanceIdentifier path) ''' ''' /* #################### RESTRICTIONS #################### */ private def restrictions(TypeDefinition type) ''' «type.toLength» «type.toRange» ''' private def dispatch toLength(TypeDefinition type) { } private def dispatch toLength(BinaryTypeDefinition type) ''' «type.lengthConstraints.toLengthStmt» ''' private def dispatch toLength(StringTypeDefinition type) ''' «type.lengthConstraints.toLengthStmt» ''' private def dispatch toLength(ExtendedType type) ''' «type.lengthConstraints.toLengthStmt» ''' private def dispatch toRange(TypeDefinition type) { } private def dispatch toRange(DecimalTypeDefinition type) ''' «type.rangeConstraints.toRangeStmt» ''' private def dispatch toRange(IntegerTypeDefinition type) ''' «type.rangeConstraints.toRangeStmt» ''' private def dispatch toRange(UnsignedIntegerTypeDefinition type) ''' «type.rangeConstraints.toRangeStmt» ''' private def dispatch toRange(ExtendedType type) ''' «type.rangeConstraints.toRangeStmt» ''' def toLengthStmt(Collection lengths) ''' «IF lengths != null && !lengths.empty» «listItem("Length restrictions")»
      «FOR length : lengths»
    • «IF length.min == length.max» «length.min» «ELSE» <«length.min», «length.max»> «ENDIF»
    • «ENDFOR»
    «ENDIF» ''' def toRangeStmt(Collection ranges) ''' «IF ranges != null && !ranges.empty» «listItem("Range restrictions")»
      «FOR range : ranges»
    • «IF range.min == range.max» «range.min» «ELSE» <«range.min», «range.max»> «ENDIF»
    • «ENDFOR»
    «ENDIF» ''' /* #################### UTILITY #################### */ private def String strong(CharSequence str) '''«str»''' private def italic(CharSequence str) '''«str»''' private def pre(CharSequence str) '''
    «str»
    ''' def CharSequence descAndRefLi(SchemaNode node) ''' «listItem(node.description)» «listItem("Reference", node.reference)» ''' def CharSequence descAndRef(SchemaNode node) ''' «node.description» «IF node.reference !== null» Reference «node.reference» «ENDIF» ''' private def listItem(String value) ''' «IF value !== null && !value.empty»
  • «value»
  • «ENDIF» ''' private def listItem(String name, String value) ''' «IF value !== null && !value.empty»
  • «name»: «value»
  • «ENDIF» ''' private def String nodeSchemaPathToPath(DataSchemaNode node, Map childNodes) { if (node instanceof ChoiceNode || node instanceof ChoiceCaseNode) { return null } val path = node.path.path val absolute = node.path.absolute; var StringBuilder result = new StringBuilder if (absolute) { result.append("/") } if (path !== null && !path.empty) { val List actual = new ArrayList() var i = 0; for (pathElement : path) { actual.add(pathElement) val DataSchemaNode nodeByPath = childNodes.get(new SchemaPath(actual, absolute)) if (!(nodeByPath instanceof ChoiceNode) && !(nodeByPath instanceof ChoiceCaseNode)) { result.append(pathElement.localName) if (i != path.size - 1) { result.append("/") } } i = i + 1 } } return result.toString } private def void collectChildNodes(Collection source, Map destination) { for (node : source) { destination.put(node.path, node) if (node instanceof DataNodeContainer) { collectChildNodes((node as DataNodeContainer).childNodes, destination) } if (node instanceof ChoiceNode) { val List choiceCases = new ArrayList() for (caseNode : (node as ChoiceNode).cases) { choiceCases.add(caseNode) } collectChildNodes(choiceCases, destination) } } } private def dispatch addedByInfo(SchemaNode node) ''' ''' private def dispatch addedByInfo(DataSchemaNode node) ''' «IF node.augmenting»(A)«ENDIF»«IF node.addedByUses»(U)«ENDIF» ''' private def dispatch isAddedBy(SchemaNode node) { return false; } private def dispatch isAddedBy(DataSchemaNode node) { if (node.augmenting || node.addedByUses) { return true } else { return false; } } private def dispatch nodeName(SchemaNode node) ''' «IF node.isAddedBy» «italic(node.QName.localName)»«node.addedByInfo» «ELSE» «node.QName.localName»«node.addedByInfo» «ENDIF» ''' private def dispatch nodeName(ContainerSchemaNode node) ''' «IF node.isAddedBy» «strong(italic(node.QName.localName))»«node.addedByInfo» «ELSE» «strong(node.QName.localName)»«node.addedByInfo» «ENDIF» ''' private def dispatch nodeName(ListSchemaNode node) ''' «IF node.isAddedBy» «strong(italic(node.QName.localName))» «IF node.keyDefinition !== null && !node.keyDefinition.empty»«node.listKeys»«ENDIF»«node.addedByInfo» «ELSE» «strong(node.QName.localName)» «IF node.keyDefinition !== null && !node.keyDefinition.empty»«node.listKeys»«ENDIF» «ENDIF» ''' }