1 package org.opendaylight.yangtools.yang.unified.doc.generator
\r
3 import org.opendaylight.yangtools.yang.model.api.SchemaContext
\r
6 import org.opendaylight.yangtools.yang.model.api.Module
\r
7 import java.io.IOException
\r
8 import java.util.HashSet
\r
9 import java.io.FileWriter
\r
10 import java.io.BufferedWriter
\r
11 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode
\r
12 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
\r
13 import org.opendaylight.yangtools.yang.model.api.TypeDefinition
\r
14 import org.opendaylight.yangtools.yang.model.api.SchemaNode
\r
15 import org.opendaylight.yangtools.yang.model.util.ExtendedType
\r
16 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition
\r
17 import java.text.SimpleDateFormat
\r
18 import java.util.Collection
\r
19 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint
\r
20 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition
\r
21 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition
\r
22 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint
\r
23 import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition
\r
24 import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition
\r
25 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition
\r
26 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer
\r
27 import org.slf4j.LoggerFactory
\r
28 import org.slf4j.Logger
\r
29 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema
\r
30 import java.util.List
\r
31 import org.opendaylight.yangtools.yang.common.QName
\r
32 import org.opendaylight.yangtools.yang.model.api.RpcDefinition
\r
33 import org.opendaylight.yangtools.yang.model.api.ExtensionDefinition
\r
34 import java.util.ArrayList
\r
35 import java.util.Map
\r
36 import org.opendaylight.yangtools.yang.model.api.SchemaPath
\r
38 import org.opendaylight.yangtools.yang.model.api.ChoiceNode
\r
39 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode
\r
40 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode
\r
41 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier
\r
42 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates
\r
43 import java.util.LinkedHashMap
\r
44 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier
\r
45 import com.google.common.collect.FluentIterable
\r
46 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode
\r
47 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode
\r
48 import java.net.URLEncoder
\r
49 import javax.swing.text.StyledEditorKit.ForegroundAction
\r
51 class GeneratorImpl {
\r
54 static val REVISION_FORMAT = new SimpleDateFormat("yyyy-MM-dd")
\r
55 static val Logger LOG = LoggerFactory.getLogger(GeneratorImpl)
\r
56 var Module currentModule;
\r
59 def generate(SchemaContext context, File targetPath, Set<Module> modulesToGen) throws IOException {
\r
62 val it = new HashSet;
\r
63 for (module : modulesToGen) {
\r
64 add(module.generateDocumentation());
\r
69 def generateDocumentation(Module module) {
\r
70 val destination = new File(path, '''«module.name».html''')
\r
72 val fw = new FileWriter(destination)
\r
73 destination.createNewFile();
\r
74 val bw = new BufferedWriter(fw)
\r
75 currentModule = module;
\r
76 bw.append(module.generate);
\r
79 } catch (IOException e) {
\r
80 LOG.error(e.getMessage());
\r
85 def generate(Module module) '''
\r
89 <title>«module.name»</title>
\r
97 def body(Module module) '''
\r
100 «typeDefinitions(module)»
\r
102 «identities(module)»
\r
104 «groupings(module)»
\r
106 «dataStore(module)»
\r
108 «childNodes(module)»
\r
110 «notifications(module)»
\r
112 «augmentations(module)»
\r
116 «extensions(module)»
\r
123 def typeDefinitions(Module module) {
\r
124 val Set<TypeDefinition<?>> typedefs = module.typeDefinitions
\r
125 if (typedefs.empty) {
\r
129 <h2>Type Definitions</h2>
\r
131 «FOR typedef : typedefs»
\r
133 «strong("typedef " + typedef.QName.localName)»
\r
135 «typedef.descAndRef»
\r
136 «typedef.restrictions»
\r
144 private def identities(Module module) {
\r
145 if (module.identities.empty) {
\r
149 <h2>Identities</h2>
\r
151 «FOR identity : module.identities»
\r
153 «strong("identity " + identity.QName.localName)»
\r
155 «identity.descAndRef»
\r
156 «IF identity.baseIdentity != null»
\r
157 «listItem("base", identity.baseIdentity.QName.localName)»
\r
166 private def groupings(Module module) {
\r
167 if (module.groupings.empty) {
\r
173 «FOR grouping : module.groupings»
\r
175 «strong("grouping " + grouping.QName.localName)»
\r
177 «grouping.descAndRef»
\r
185 def dataStore(Module module) {
\r
186 if (module.childNodes.empty) {
\r
190 <h2>Datastore Structure</h2>
\r
195 def augmentations(Module module) {
\r
196 if (module.augmentations.empty) {
\r
200 <h2>Augmentations</h2>
\r
203 «FOR augment : module.augmentations»
\r
206 «augment.augmentationInfo(InstanceIdentifier.builder().toInstance())»
\r
213 def notifications(Module module) {
\r
214 val Set<NotificationDefinition> notificationdefs = module.notifications
\r
215 if (notificationdefs.empty) {
\r
220 <h2>Notifications</h2>
\r
221 «FOR notificationdef : notificationdefs»
\r
223 <h3>«notificationdef.nodeName»</h3>
\r
224 «notificationdef.notificationInfo(InstanceIdentifier.builder().node(notificationdef.QName).toInstance())»
\r
229 def rpcs(Module module) {
\r
230 if (module.rpcs.empty) {
\r
235 <h2>RPC Definitions</h2>
\r
236 «FOR rpc : module.rpcs»
\r
237 <h3>«rpc.nodeName»</h3>
\r
238 «rpc.rpcInfo(InstanceIdentifier.builder().node(rpc.QName).toInstance())»
\r
244 def extensions(Module module) {
\r
245 if (module.extensionSchemaNodes.empty) {
\r
249 <h2>Extensions</h2>
\r
250 «FOR ext : module.extensionSchemaNodes»
\r
252 <h3>«ext.nodeName»</h3>
\r
258 def features(Module module) {
\r
259 if (module.features.empty) {
\r
266 «FOR feature : module.features»
\r
268 «strong("feature " + feature.QName.localName)»
\r
270 «feature.descAndRef»
\r
278 def header(Module module) '''
\r
279 <h1>«module.name»</h1>
\r
281 <h2>Base Information</h2>
\r
284 <dd>«pre(module.prefix)»</dd>
\r
286 <dd>«pre(module.namespace.toString)»</dd>
\r
288 <dd>«pre(REVISION_FORMAT.format(module.revision))»</dd>
\r
290 «FOR imp : module.imports BEFORE "<dt>Imports</dt>" »
\r
291 <dd>«code(imp.prefix)» = «code(imp.moduleName)»</dd>
\r
296 def code(String string) '''<code>«string»</code>'''
\r
298 def process(Module module) {
\r
299 throw new UnsupportedOperationException("TODO: auto-generated method stub")
\r
302 def CharSequence tree(Module module) '''
\r
303 «strong("module " + module.name)»
\r
304 «module.childNodes.treeSet(InstanceIdentifier.builder.toInstance())»
\r
307 private def dispatch CharSequence tree(ChoiceNode node,InstanceIdentifier path) '''
\r
308 «node.nodeName» (choice)
\r
309 «casesTree(node.cases,path)»
\r
312 def casesTree(Set<ChoiceCaseNode> nodes,InstanceIdentifier path) '''
\r
317 «node.childNodes.treeSet(path)»
\r
323 private def dispatch CharSequence tree(DataSchemaNode node,InstanceIdentifier path) '''
\r
327 private def dispatch CharSequence tree(ListSchemaNode node,InstanceIdentifier path) '''
\r
328 «val newPath = path.append(node)»
\r
329 «localLink(newPath,node.nodeName)»
\r
330 «node.childNodes.treeSet(newPath)»
\r
333 private def dispatch CharSequence tree(ContainerSchemaNode node,InstanceIdentifier path) '''
\r
334 «val newPath = path.append(node)»
\r
335 «localLink(newPath,node.nodeName)»
\r
336 «node.childNodes.treeSet(newPath)»
\r
339 def CharSequence childNodes(Module module) '''
\r
340 «val childNodes = module.childNodes»
\r
341 «IF childNodes !== null && !childNodes.empty»
\r
342 <h2>Child nodes</h2>
\r
344 «childNodes.printChildren(2,InstanceIdentifier.builder().toInstance())»
\r
348 def CharSequence printChildren(Set<DataSchemaNode> nodes, int level, InstanceIdentifier path) {
\r
349 val leafNodes = nodes.filter(LeafSchemaNode)
\r
350 val leafListNodes = nodes.filter(LeafListSchemaNode)
\r
351 val choices = nodes.filter(ChoiceNode)
\r
352 val containers = nodes.filter(ContainerSchemaNode)
\r
353 val lists = nodes.filter(ListSchemaNode)
\r
355 <h3>Direct children</h3>
\r
357 «FOR childNode : leafNodes»
\r
358 «childNode.printShortInfo(level,path)»
\r
360 «FOR childNode : leafListNodes»
\r
361 «childNode.printShortInfo(level,path)»
\r
363 «FOR childNode : containers»
\r
364 «childNode.printShortInfo(level,path)»
\r
366 «FOR childNode : lists»
\r
367 «childNode.printShortInfo(level,path)»
\r
371 «IF !path.path.empty»
\r
372 <h3>XML example</h3>
\r
373 «nodes.xmlExample(path.path.last.nodeType,path)»
\r
376 «FOR childNode : containers»
\r
377 «childNode.printInfo(level,path)»
\r
379 «FOR childNode : lists»
\r
380 «childNode.printInfo(level,path)»
\r
386 def CharSequence xmlExample(Set<DataSchemaNode> nodes, QName name,InstanceIdentifier path) '''
\r
388 «xmlExampleTag(name,nodes.xmplExampleTags(path))»
\r
392 def CharSequence xmplExampleTags(Set<DataSchemaNode> nodes, InstanceIdentifier identifier) '''
\r
393 <!-- Child nodes -->
\r
395 <!-- «node.QName.localName» -->
\r
396 «node.asXmlExampleTag(identifier)»
\r
401 private def dispatch CharSequence asXmlExampleTag(LeafSchemaNode node, InstanceIdentifier identifier) '''
\r
402 «node.QName.xmlExampleTag("...")»
\r
405 private def dispatch CharSequence asXmlExampleTag(LeafListSchemaNode node, InstanceIdentifier identifier) '''
\r
406 <!-- This node could appear multiple times -->
\r
407 «node.QName.xmlExampleTag("...")»
\r
410 private def dispatch CharSequence asXmlExampleTag(ContainerSchemaNode node, InstanceIdentifier identifier) '''
\r
411 <!-- See «localLink(identifier.append(node),"definition")» for child nodes. -->
\r
412 «node.QName.xmlExampleTag("...")»
\r
416 private def dispatch CharSequence asXmlExampleTag(ListSchemaNode node, InstanceIdentifier identifier) '''
\r
417 <!-- See «localLink(identifier.append(node),"definition")» for child nodes. -->
\r
418 <!-- This node could appear multiple times -->
\r
419 «node.QName.xmlExampleTag("...")»
\r
423 private def dispatch CharSequence asXmlExampleTag(DataSchemaNode node, InstanceIdentifier identifier) '''
\r
428 def xmlExampleTag(QName name, CharSequence data) {
\r
429 return '''<«name.localName» xmlns="«name.namespace»">«data»</«name.localName»>'''
\r
432 private def dispatch CharSequence printInfo(ContainerSchemaNode node, int level, InstanceIdentifier path) '''
\r
433 «val newPath = path.append(node)»
\r
434 «header(level,newPath)»
\r
437 <dd>«newPath.asXmlPath»</dd>
\r
438 <dt>Restconf path</dt>
\r
439 <dd>«code(newPath.asRestconfPath)»</dd>
\r
441 «node.childNodes.printChildren(level,newPath)»
\r
444 def header(int level,QName name) '''<h«level»>«name.localName»</h«level»>'''
\r
447 def header(int level,InstanceIdentifier name)
\r
449 <h«level» id="«FOR cmp : name.path SEPARATOR "/"»«cmp.nodeType.localName»«ENDFOR»">
\r
450 «FOR cmp : name.path SEPARATOR "/"»«cmp.nodeType.localName»«ENDFOR»
\r
454 private def dispatch CharSequence printInfo(ListSchemaNode node, int level, InstanceIdentifier path) '''
\r
455 «val newPath = path.append(node)»
\r
456 «header(level,newPath)»
\r
459 <dd>«newPath.asXmlPath»</dd>
\r
460 <dt>Restconf path</dt>
\r
461 <dd>«code(newPath.asRestconfPath)»</dd>
\r
463 «node.childNodes.printChildren(level,newPath)»
\r
466 def CharSequence printShortInfo(ContainerSchemaNode node, int level, InstanceIdentifier path) {
\r
467 val newPath = path.append(node);
\r
469 <li>«strong(localLink(newPath,node.QName.localName))» (container)</li>
\r
473 def CharSequence printShortInfo(ListSchemaNode node, int level, InstanceIdentifier path) {
\r
474 val newPath = path.append(node);
\r
476 <li>«strong(localLink(newPath,node.QName.localName))» (list)</li>
\r
480 def CharSequence printShortInfo(LeafSchemaNode node, int level, InstanceIdentifier path) {
\r
482 <li>«strong((node.QName.localName))» (leaf)</li>
\r
486 def CharSequence printShortInfo(LeafListSchemaNode node, int level, InstanceIdentifier path) {
\r
488 <li>«strong((node.QName.localName))» (leaf-list)</li>
\r
492 def CharSequence localLink(InstanceIdentifier identifier, CharSequence text) '''
\r
493 <a href="#«FOR cmp : identifier.path SEPARATOR "/"»«cmp.nodeType.localName»«ENDFOR»">«text»</a>
\r
497 private def dispatch InstanceIdentifier append(InstanceIdentifier identifier, ContainerSchemaNode node) {
\r
498 val pathArguments = new ArrayList(identifier.path)
\r
499 pathArguments.add(new NodeIdentifier(node.QName));
\r
500 return new InstanceIdentifier(pathArguments);
\r
503 private def dispatch InstanceIdentifier append(InstanceIdentifier identifier, ListSchemaNode node) {
\r
504 val pathArguments = new ArrayList(identifier.path)
\r
505 val keyValues = new LinkedHashMap<QName,Object>();
\r
506 if(node.keyDefinition != null) {
\r
507 for(definition : node.keyDefinition) {
\r
508 keyValues.put(definition,new Object);
\r
511 pathArguments.add(new NodeIdentifierWithPredicates(node.QName,keyValues));
\r
512 return new InstanceIdentifier(pathArguments);
\r
516 def asXmlPath(InstanceIdentifier identifier) {
\r
520 def asRestconfPath(InstanceIdentifier identifier) {
\r
521 val it = new StringBuilder();
\r
522 append(currentModule.name)
\r
524 var previous = false;
\r
525 for(arg : identifier.path) {
\r
526 if(previous) append("/")
\r
527 append(arg.nodeType.localName);
\r
529 if(arg instanceof NodeIdentifierWithPredicates) {
\r
530 val nodeIdentifier = arg as NodeIdentifierWithPredicates;
\r
531 for(qname : nodeIdentifier.keyValues.keySet) {
\r
533 append(qname.localName)
\r
539 return it.toString;
\r
543 private def dispatch CharSequence printInfo(DataSchemaNode node, int level, InstanceIdentifier path) '''
\r
544 «header(level+1,node.QName)»
\r
551 def CharSequence childNodesInfoTree(Map<SchemaPath, DataSchemaNode> childNodes) '''
\r
552 «IF childNodes !== null && !childNodes.empty»
\r
553 «FOR child : childNodes.values»
\r
554 «childInfo(child, childNodes)»
\r
559 def CharSequence childInfo(DataSchemaNode node, Map<SchemaPath, DataSchemaNode> childNodes) '''
\r
560 «val String path = nodeSchemaPathToPath(node, childNodes)»
\r
571 private def CharSequence treeSet(Collection<DataSchemaNode> childNodes, InstanceIdentifier path) '''
\r
572 «IF childNodes !== null && !childNodes.empty»
\r
574 «FOR child : childNodes»
\r
583 def listKeys(ListSchemaNode node) '''
\r
584 [«FOR key : node.keyDefinition SEPARATOR " "»«key.localName»«ENDFOR»]
\r
587 private def CharSequence augmentationInfo(AugmentationSchema augment, InstanceIdentifier path) '''
\r
589 «listItem(augment.description)»
\r
590 «listItem("Reference", augment.reference)»
\r
591 «IF augment.whenCondition !== null»
\r
592 «listItem("When", augment.whenCondition.toString)»
\r
595 Path «augment.targetPath.path.pathToTree»
\r
599 «augment.childNodes.treeSet(path)»
\r
604 private def CharSequence notificationInfo(NotificationDefinition notification,InstanceIdentifier path) '''
\r
606 «notification.descAndRef»
\r
609 «notification.childNodes.treeSet(path)»
\r
614 private def CharSequence rpcInfo(RpcDefinition rpc,InstanceIdentifier path) '''
\r
618 «rpc.input.tree(path)»
\r
621 «rpc.output.tree(path)»
\r
626 private def CharSequence extensionInfo(ExtensionDefinition ext, InstanceIdentifier path) '''
\r
629 «listItem("Argument", ext.argument)»
\r
633 private def dispatch CharSequence tree(Void obj, InstanceIdentifier path) '''
\r
638 /* #################### RESTRICTIONS #################### */
\r
639 private def restrictions(TypeDefinition<?> type) '''
\r
644 private def dispatch toLength(TypeDefinition<?> type) {
\r
647 private def dispatch toLength(BinaryTypeDefinition type) '''
\r
648 «type.lengthConstraints.toLengthStmt»
\r
651 private def dispatch toLength(StringTypeDefinition type) '''
\r
652 «type.lengthConstraints.toLengthStmt»
\r
655 private def dispatch toLength(ExtendedType type) '''
\r
656 «type.lengthConstraints.toLengthStmt»
\r
659 private def dispatch toRange(TypeDefinition<?> type) {
\r
662 private def dispatch toRange(DecimalTypeDefinition type) '''
\r
663 «type.rangeConstraints.toRangeStmt»
\r
666 private def dispatch toRange(IntegerTypeDefinition type) '''
\r
667 «type.rangeConstraints.toRangeStmt»
\r
670 private def dispatch toRange(UnsignedIntegerTypeDefinition type) '''
\r
671 «type.rangeConstraints.toRangeStmt»
\r
674 private def dispatch toRange(ExtendedType type) '''
\r
675 «type.rangeConstraints.toRangeStmt»
\r
678 def toLengthStmt(Collection<LengthConstraint> lengths) '''
\r
679 «IF lengths != null && !lengths.empty»
\r
680 «listItem("Length restrictions")»
\r
682 «FOR length : lengths»
\r
684 «IF length.min == length.max»
\r
687 <«length.min», «length.max»>
\r
695 def toRangeStmt(Collection<RangeConstraint> ranges) '''
\r
696 «IF ranges != null && !ranges.empty»
\r
697 «listItem("Range restrictions")»
\r
699 «FOR range : ranges»
\r
701 «IF range.min == range.max»
\r
704 <«range.min», «range.max»>
\r
714 /* #################### UTILITY #################### */
\r
715 private def String strong(CharSequence str) '''<strong>«str»</strong>'''
\r
716 private def italic(CharSequence str) '''<i>«str»</i>'''
\r
717 private def pre(CharSequence str) '''<pre>«str»</pre>'''
\r
719 def CharSequence descAndRef(SchemaNode node) '''
\r
720 «listItem(node.description)»
\r
721 «listItem("Reference", node.reference)»
\r
724 private def listItem(String value) '''
\r
725 «IF value !== null && !value.empty»
\r
732 private def listItem(String name, String value) '''
\r
733 «IF value !== null && !value.empty»
\r
745 private def String nodeSchemaPathToPath(DataSchemaNode node, Map<SchemaPath, DataSchemaNode> childNodes) {
\r
746 if (node instanceof ChoiceNode || node instanceof ChoiceCaseNode) {
\r
750 val path = node.path.path
\r
751 val absolute = node.path.absolute;
\r
752 var StringBuilder result = new StringBuilder
\r
756 if (path !== null && !path.empty) {
\r
757 val List<QName> actual = new ArrayList()
\r
759 for (pathElement : path) {
\r
760 actual.add(pathElement)
\r
761 val DataSchemaNode nodeByPath = childNodes.get(new SchemaPath(actual, absolute))
\r
762 if (!(nodeByPath instanceof ChoiceNode) && !(nodeByPath instanceof ChoiceCaseNode)) {
\r
763 result.append(pathElement.localName)
\r
764 if (i != path.size - 1) {
\r
771 return result.toString
\r
774 private def void collectChildNodes(Collection<DataSchemaNode> source, Map<SchemaPath, DataSchemaNode> destination) {
\r
775 for (node : source) {
\r
776 destination.put(node.path, node)
\r
777 if (node instanceof DataNodeContainer) {
\r
778 collectChildNodes((node as DataNodeContainer).childNodes, destination)
\r
780 if (node instanceof ChoiceNode) {
\r
781 val List<DataSchemaNode> choiceCases = new ArrayList()
\r
782 for (caseNode : (node as ChoiceNode).cases) {
\r
783 choiceCases.add(caseNode)
\r
785 collectChildNodes(choiceCases, destination)
\r
790 private def CharSequence pathToTree(List<QName> path) '''
\r
791 «IF path !== null && !path.empty»
\r
793 «FOR pathElement : path»
\r
795 «pathElement.namespace» «pathElement.localName»
\r
802 private def dispatch addedByInfo(SchemaNode node) '''
\r
805 private def dispatch addedByInfo(DataSchemaNode node) '''
\r
806 «IF node.augmenting»(A)«ENDIF»«IF node.addedByUses»(U)«ENDIF»
\r
809 private def dispatch isAddedBy(SchemaNode node) {
\r
813 private def dispatch isAddedBy(DataSchemaNode node) {
\r
814 if (node.augmenting || node.addedByUses) {
\r
821 private def dispatch nodeName(SchemaNode node) '''
\r
822 «IF node.isAddedBy»
\r
823 «italic(node.QName.localName)»«node.addedByInfo»
\r
825 «node.QName.localName»«node.addedByInfo»
\r
829 private def dispatch nodeName(ContainerSchemaNode node) '''
\r
830 «IF node.isAddedBy»
\r
831 «strong(italic(node.QName.localName))»«node.addedByInfo»
\r
833 «strong(node.QName.localName)»«node.addedByInfo»
\r
837 private def dispatch nodeName(ListSchemaNode node) '''
\r
838 «IF node.isAddedBy»
\r
839 «strong(italic(node.QName.localName))» «IF node.keyDefinition !== null && !node.keyDefinition.empty»«node.listKeys»«ENDIF»«node.addedByInfo»
\r
841 «strong(node.QName.localName)» «IF node.keyDefinition !== null && !node.keyDefinition.empty»«node.listKeys»«ENDIF»
\r