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.BufferedWriter
\r
10 import java.io.OutputStream;
\r
11 import java.io.OutputStreamWriter;
\r
12 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode
\r
13 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
\r
14 import org.opendaylight.yangtools.yang.model.api.TypeDefinition
\r
15 import org.opendaylight.yangtools.yang.model.api.SchemaNode
\r
16 import org.opendaylight.yangtools.yang.model.util.ExtendedType
\r
17 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition
\r
18 import java.text.SimpleDateFormat
\r
19 import java.util.Collection
\r
20 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint
\r
21 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition
\r
22 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition
\r
23 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint
\r
24 import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition
\r
25 import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition
\r
26 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition
\r
27 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer
\r
28 import org.slf4j.LoggerFactory
\r
29 import org.slf4j.Logger
\r
30 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema
\r
31 import java.util.List
\r
32 import org.opendaylight.yangtools.yang.common.QName
\r
33 import org.opendaylight.yangtools.yang.model.api.RpcDefinition
\r
34 import org.opendaylight.yangtools.yang.model.api.ExtensionDefinition
\r
35 import java.util.ArrayList
\r
36 import java.util.Map
\r
37 import org.opendaylight.yangtools.yang.model.api.SchemaPath
\r
39 import org.sonatype.plexus.build.incremental.BuildContext;
\r
40 import org.sonatype.plexus.build.incremental.DefaultBuildContext;
\r
42 import org.opendaylight.yangtools.yang.model.api.ChoiceNode
\r
43 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode
\r
44 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode
\r
45 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier
\r
46 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates
\r
47 import java.util.LinkedHashMap
\r
48 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier
\r
49 import com.google.common.collect.FluentIterable
\r
50 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode
\r
51 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode
\r
52 import java.net.URLEncoder
\r
53 import javax.swing.text.StyledEditorKit.ForegroundAction
\r
55 class GeneratorImpl {
\r
58 static val REVISION_FORMAT = new SimpleDateFormat("yyyy-MM-dd")
\r
59 static val Logger LOG = LoggerFactory.getLogger(GeneratorImpl)
\r
60 static val BuildContext CTX = new DefaultBuildContext();
\r
61 var Module currentModule;
\r
64 def generate(SchemaContext context, File targetPath, Set<Module> modulesToGen) throws IOException {
\r
67 val it = new HashSet;
\r
68 for (module : modulesToGen) {
\r
69 add(module.generateDocumentation());
\r
74 def generateDocumentation(Module module) {
\r
75 val destination = new File(path, '''«module.name».html''')
\r
77 val fw = new OutputStreamWriter(CTX.newFileOutputStream(destination))
\r
78 val bw = new BufferedWriter(fw)
\r
79 currentModule = module;
\r
80 bw.append(module.generate);
\r
83 } catch (IOException e) {
\r
84 LOG.error(e.getMessage());
\r
89 def generate(Module module) '''
\r
93 <title>«module.name»</title>
\r
101 def body(Module module) '''
\r
104 «typeDefinitions(module)»
\r
106 «identities(module)»
\r
108 «groupings(module)»
\r
110 «dataStore(module)»
\r
112 «childNodes(module)»
\r
114 «notifications(module)»
\r
116 «augmentations(module)»
\r
120 «extensions(module)»
\r
127 def typeDefinitions(Module module) {
\r
128 val Set<TypeDefinition<?>> typedefs = module.typeDefinitions
\r
129 if (typedefs.empty) {
\r
133 <h2>Type Definitions</h2>
\r
135 «FOR typedef : typedefs»
\r
137 «strong("typedef " + typedef.QName.localName)»
\r
139 «typedef.descAndRef»
\r
140 «typedef.restrictions»
\r
148 private def identities(Module module) {
\r
149 if (module.identities.empty) {
\r
153 <h2>Identities</h2>
\r
155 «FOR identity : module.identities»
\r
157 «strong("identity " + identity.QName.localName)»
\r
159 «identity.descAndRef»
\r
160 «IF identity.baseIdentity != null»
\r
161 «listItem("base", identity.baseIdentity.QName.localName)»
\r
170 private def groupings(Module module) {
\r
171 if (module.groupings.empty) {
\r
177 «FOR grouping : module.groupings»
\r
179 «strong("grouping " + grouping.QName.localName)»
\r
181 «grouping.descAndRef»
\r
189 def dataStore(Module module) {
\r
190 if (module.childNodes.empty) {
\r
194 <h2>Datastore Structure</h2>
\r
199 def augmentations(Module module) {
\r
200 if (module.augmentations.empty) {
\r
204 <h2>Augmentations</h2>
\r
207 «FOR augment : module.augmentations»
\r
210 «augment.augmentationInfo(InstanceIdentifier.builder().toInstance())»
\r
217 def notifications(Module module) {
\r
218 val Set<NotificationDefinition> notificationdefs = module.notifications
\r
219 if (notificationdefs.empty) {
\r
224 <h2>Notifications</h2>
\r
225 «FOR notificationdef : notificationdefs»
\r
227 <h3>«notificationdef.nodeName»</h3>
\r
228 «notificationdef.notificationInfo(InstanceIdentifier.builder().node(notificationdef.QName).toInstance())»
\r
233 def rpcs(Module module) {
\r
234 if (module.rpcs.empty) {
\r
239 <h2>RPC Definitions</h2>
\r
240 «FOR rpc : module.rpcs»
\r
241 <h3>«rpc.nodeName»</h3>
\r
242 «rpc.rpcInfo(InstanceIdentifier.builder().node(rpc.QName).toInstance())»
\r
248 def extensions(Module module) {
\r
249 if (module.extensionSchemaNodes.empty) {
\r
253 <h2>Extensions</h2>
\r
254 «FOR ext : module.extensionSchemaNodes»
\r
256 <h3>«ext.nodeName»</h3>
\r
262 def features(Module module) {
\r
263 if (module.features.empty) {
\r
270 «FOR feature : module.features»
\r
272 «strong("feature " + feature.QName.localName)»
\r
274 «feature.descAndRef»
\r
282 def header(Module module) '''
\r
283 <h1>«module.name»</h1>
\r
285 <h2>Base Information</h2>
\r
288 <dd>«pre(module.prefix)»</dd>
\r
290 <dd>«pre(module.namespace.toString)»</dd>
\r
292 <dd>«pre(REVISION_FORMAT.format(module.revision))»</dd>
\r
294 «FOR imp : module.imports BEFORE "<dt>Imports</dt>" »
\r
295 <dd>«code(imp.prefix)» = «code(imp.moduleName)»</dd>
\r
300 def code(String string) '''<code>«string»</code>'''
\r
302 def process(Module module) {
\r
303 throw new UnsupportedOperationException("TODO: auto-generated method stub")
\r
306 def CharSequence tree(Module module) '''
\r
307 «strong("module " + module.name)»
\r
308 «module.childNodes.treeSet(InstanceIdentifier.builder.toInstance())»
\r
311 private def dispatch CharSequence tree(ChoiceNode node,InstanceIdentifier path) '''
\r
312 «node.nodeName» (choice)
\r
313 «casesTree(node.cases,path)»
\r
316 def casesTree(Set<ChoiceCaseNode> nodes,InstanceIdentifier path) '''
\r
321 «node.childNodes.treeSet(path)»
\r
327 private def dispatch CharSequence tree(DataSchemaNode node,InstanceIdentifier path) '''
\r
331 private def dispatch CharSequence tree(ListSchemaNode node,InstanceIdentifier path) '''
\r
332 «val newPath = path.append(node)»
\r
333 «localLink(newPath,node.nodeName)»
\r
334 «node.childNodes.treeSet(newPath)»
\r
337 private def dispatch CharSequence tree(ContainerSchemaNode node,InstanceIdentifier path) '''
\r
338 «val newPath = path.append(node)»
\r
339 «localLink(newPath,node.nodeName)»
\r
340 «node.childNodes.treeSet(newPath)»
\r
343 def CharSequence childNodes(Module module) '''
\r
344 «val childNodes = module.childNodes»
\r
345 «IF childNodes !== null && !childNodes.empty»
\r
346 <h2>Child nodes</h2>
\r
348 «childNodes.printChildren(2,InstanceIdentifier.builder().toInstance())»
\r
352 def CharSequence printChildren(Set<DataSchemaNode> nodes, int level, InstanceIdentifier path) {
\r
353 val leafNodes = nodes.filter(LeafSchemaNode)
\r
354 val leafListNodes = nodes.filter(LeafListSchemaNode)
\r
355 val choices = nodes.filter(ChoiceNode)
\r
356 val containers = nodes.filter(ContainerSchemaNode)
\r
357 val lists = nodes.filter(ListSchemaNode)
\r
359 <h3>Direct children</h3>
\r
361 «FOR childNode : leafNodes»
\r
362 «childNode.printShortInfo(level,path)»
\r
364 «FOR childNode : leafListNodes»
\r
365 «childNode.printShortInfo(level,path)»
\r
367 «FOR childNode : containers»
\r
368 «childNode.printShortInfo(level,path)»
\r
370 «FOR childNode : lists»
\r
371 «childNode.printShortInfo(level,path)»
\r
375 «IF !path.path.empty»
\r
376 <h3>XML example</h3>
\r
377 «nodes.xmlExample(path.path.last.nodeType,path)»
\r
380 «FOR childNode : containers»
\r
381 «childNode.printInfo(level,path)»
\r
383 «FOR childNode : lists»
\r
384 «childNode.printInfo(level,path)»
\r
390 def CharSequence xmlExample(Set<DataSchemaNode> nodes, QName name,InstanceIdentifier path) '''
\r
392 «xmlExampleTag(name,nodes.xmplExampleTags(path))»
\r
396 def CharSequence xmplExampleTags(Set<DataSchemaNode> nodes, InstanceIdentifier identifier) '''
\r
397 <!-- Child nodes -->
\r
399 <!-- «node.QName.localName» -->
\r
400 «node.asXmlExampleTag(identifier)»
\r
405 private def dispatch CharSequence asXmlExampleTag(LeafSchemaNode node, InstanceIdentifier identifier) '''
\r
406 «node.QName.xmlExampleTag("...")»
\r
409 private def dispatch CharSequence asXmlExampleTag(LeafListSchemaNode node, InstanceIdentifier identifier) '''
\r
410 <!-- This node could appear multiple times -->
\r
411 «node.QName.xmlExampleTag("...")»
\r
414 private def dispatch CharSequence asXmlExampleTag(ContainerSchemaNode node, InstanceIdentifier identifier) '''
\r
415 <!-- See «localLink(identifier.append(node),"definition")» for child nodes. -->
\r
416 «node.QName.xmlExampleTag("...")»
\r
420 private def dispatch CharSequence asXmlExampleTag(ListSchemaNode node, InstanceIdentifier identifier) '''
\r
421 <!-- See «localLink(identifier.append(node),"definition")» for child nodes. -->
\r
422 <!-- This node could appear multiple times -->
\r
423 «node.QName.xmlExampleTag("...")»
\r
427 private def dispatch CharSequence asXmlExampleTag(DataSchemaNode node, InstanceIdentifier identifier) '''
\r
432 def xmlExampleTag(QName name, CharSequence data) {
\r
433 return '''<«name.localName» xmlns="«name.namespace»">«data»</«name.localName»>'''
\r
436 private def dispatch CharSequence printInfo(ContainerSchemaNode node, int level, InstanceIdentifier path) '''
\r
437 «val newPath = path.append(node)»
\r
438 «header(level,newPath)»
\r
441 <dd>«newPath.asXmlPath»</dd>
\r
442 <dt>Restconf path</dt>
\r
443 <dd>«code(newPath.asRestconfPath)»</dd>
\r
445 «node.childNodes.printChildren(level,newPath)»
\r
448 def header(int level,QName name) '''<h«level»>«name.localName»</h«level»>'''
\r
451 def header(int level,InstanceIdentifier name)
\r
453 <h«level» id="«FOR cmp : name.path SEPARATOR "/"»«cmp.nodeType.localName»«ENDFOR»">
\r
454 «FOR cmp : name.path SEPARATOR "/"»«cmp.nodeType.localName»«ENDFOR»
\r
458 private def dispatch CharSequence printInfo(ListSchemaNode node, int level, InstanceIdentifier path) '''
\r
459 «val newPath = path.append(node)»
\r
460 «header(level,newPath)»
\r
463 <dd>«newPath.asXmlPath»</dd>
\r
464 <dt>Restconf path</dt>
\r
465 <dd>«code(newPath.asRestconfPath)»</dd>
\r
467 «node.childNodes.printChildren(level,newPath)»
\r
470 def CharSequence printShortInfo(ContainerSchemaNode node, int level, InstanceIdentifier path) {
\r
471 val newPath = path.append(node);
\r
473 <li>«strong(localLink(newPath,node.QName.localName))» (container)</li>
\r
477 def CharSequence printShortInfo(ListSchemaNode node, int level, InstanceIdentifier path) {
\r
478 val newPath = path.append(node);
\r
480 <li>«strong(localLink(newPath,node.QName.localName))» (list)</li>
\r
484 def CharSequence printShortInfo(LeafSchemaNode node, int level, InstanceIdentifier path) {
\r
486 <li>«strong((node.QName.localName))» (leaf)</li>
\r
490 def CharSequence printShortInfo(LeafListSchemaNode node, int level, InstanceIdentifier path) {
\r
492 <li>«strong((node.QName.localName))» (leaf-list)</li>
\r
496 def CharSequence localLink(InstanceIdentifier identifier, CharSequence text) '''
\r
497 <a href="#«FOR cmp : identifier.path SEPARATOR "/"»«cmp.nodeType.localName»«ENDFOR»">«text»</a>
\r
501 private def dispatch InstanceIdentifier append(InstanceIdentifier identifier, ContainerSchemaNode node) {
\r
502 val pathArguments = new ArrayList(identifier.path)
\r
503 pathArguments.add(new NodeIdentifier(node.QName));
\r
504 return new InstanceIdentifier(pathArguments);
\r
507 private def dispatch InstanceIdentifier append(InstanceIdentifier identifier, ListSchemaNode node) {
\r
508 val pathArguments = new ArrayList(identifier.path)
\r
509 val keyValues = new LinkedHashMap<QName,Object>();
\r
510 if(node.keyDefinition != null) {
\r
511 for(definition : node.keyDefinition) {
\r
512 keyValues.put(definition,new Object);
\r
515 pathArguments.add(new NodeIdentifierWithPredicates(node.QName,keyValues));
\r
516 return new InstanceIdentifier(pathArguments);
\r
520 def asXmlPath(InstanceIdentifier identifier) {
\r
524 def asRestconfPath(InstanceIdentifier identifier) {
\r
525 val it = new StringBuilder();
\r
526 append(currentModule.name)
\r
528 var previous = false;
\r
529 for(arg : identifier.path) {
\r
530 if(previous) append("/")
\r
531 append(arg.nodeType.localName);
\r
533 if(arg instanceof NodeIdentifierWithPredicates) {
\r
534 val nodeIdentifier = arg as NodeIdentifierWithPredicates;
\r
535 for(qname : nodeIdentifier.keyValues.keySet) {
\r
537 append(qname.localName)
\r
543 return it.toString;
\r
547 private def dispatch CharSequence printInfo(DataSchemaNode node, int level, InstanceIdentifier path) '''
\r
548 «header(level+1,node.QName)»
\r
555 def CharSequence childNodesInfoTree(Map<SchemaPath, DataSchemaNode> childNodes) '''
\r
556 «IF childNodes !== null && !childNodes.empty»
\r
557 «FOR child : childNodes.values»
\r
558 «childInfo(child, childNodes)»
\r
563 def CharSequence childInfo(DataSchemaNode node, Map<SchemaPath, DataSchemaNode> childNodes) '''
\r
564 «val String path = nodeSchemaPathToPath(node, childNodes)»
\r
575 private def CharSequence treeSet(Collection<DataSchemaNode> childNodes, InstanceIdentifier path) '''
\r
576 «IF childNodes !== null && !childNodes.empty»
\r
578 «FOR child : childNodes»
\r
587 def listKeys(ListSchemaNode node) '''
\r
588 [«FOR key : node.keyDefinition SEPARATOR " "»«key.localName»«ENDFOR»]
\r
591 private def CharSequence augmentationInfo(AugmentationSchema augment, InstanceIdentifier path) '''
\r
593 «listItem(augment.description)»
\r
594 «listItem("Reference", augment.reference)»
\r
595 «IF augment.whenCondition !== null»
\r
596 «listItem("When", augment.whenCondition.toString)»
\r
599 Path «augment.targetPath.path.pathToTree»
\r
603 «augment.childNodes.treeSet(path)»
\r
608 private def CharSequence notificationInfo(NotificationDefinition notification,InstanceIdentifier path) '''
\r
610 «notification.descAndRef»
\r
613 «notification.childNodes.treeSet(path)»
\r
618 private def CharSequence rpcInfo(RpcDefinition rpc,InstanceIdentifier path) '''
\r
622 «rpc.input.tree(path)»
\r
625 «rpc.output.tree(path)»
\r
630 private def CharSequence extensionInfo(ExtensionDefinition ext, InstanceIdentifier path) '''
\r
633 «listItem("Argument", ext.argument)»
\r
637 private def dispatch CharSequence tree(Void obj, InstanceIdentifier path) '''
\r
642 /* #################### RESTRICTIONS #################### */
\r
643 private def restrictions(TypeDefinition<?> type) '''
\r
648 private def dispatch toLength(TypeDefinition<?> type) {
\r
651 private def dispatch toLength(BinaryTypeDefinition type) '''
\r
652 «type.lengthConstraints.toLengthStmt»
\r
655 private def dispatch toLength(StringTypeDefinition type) '''
\r
656 «type.lengthConstraints.toLengthStmt»
\r
659 private def dispatch toLength(ExtendedType type) '''
\r
660 «type.lengthConstraints.toLengthStmt»
\r
663 private def dispatch toRange(TypeDefinition<?> type) {
\r
666 private def dispatch toRange(DecimalTypeDefinition type) '''
\r
667 «type.rangeConstraints.toRangeStmt»
\r
670 private def dispatch toRange(IntegerTypeDefinition type) '''
\r
671 «type.rangeConstraints.toRangeStmt»
\r
674 private def dispatch toRange(UnsignedIntegerTypeDefinition type) '''
\r
675 «type.rangeConstraints.toRangeStmt»
\r
678 private def dispatch toRange(ExtendedType type) '''
\r
679 «type.rangeConstraints.toRangeStmt»
\r
682 def toLengthStmt(Collection<LengthConstraint> lengths) '''
\r
683 «IF lengths != null && !lengths.empty»
\r
684 «listItem("Length restrictions")»
\r
686 «FOR length : lengths»
\r
688 «IF length.min == length.max»
\r
691 <«length.min», «length.max»>
\r
699 def toRangeStmt(Collection<RangeConstraint> ranges) '''
\r
700 «IF ranges != null && !ranges.empty»
\r
701 «listItem("Range restrictions")»
\r
703 «FOR range : ranges»
\r
705 «IF range.min == range.max»
\r
708 <«range.min», «range.max»>
\r
718 /* #################### UTILITY #################### */
\r
719 private def String strong(CharSequence str) '''<strong>«str»</strong>'''
\r
720 private def italic(CharSequence str) '''<i>«str»</i>'''
\r
721 private def pre(CharSequence str) '''<pre>«str»</pre>'''
\r
723 def CharSequence descAndRef(SchemaNode node) '''
\r
724 «listItem(node.description)»
\r
725 «listItem("Reference", node.reference)»
\r
728 private def listItem(String value) '''
\r
729 «IF value !== null && !value.empty»
\r
736 private def listItem(String name, String value) '''
\r
737 «IF value !== null && !value.empty»
\r
749 private def String nodeSchemaPathToPath(DataSchemaNode node, Map<SchemaPath, DataSchemaNode> childNodes) {
\r
750 if (node instanceof ChoiceNode || node instanceof ChoiceCaseNode) {
\r
754 val path = node.path.path
\r
755 val absolute = node.path.absolute;
\r
756 var StringBuilder result = new StringBuilder
\r
760 if (path !== null && !path.empty) {
\r
761 val List<QName> actual = new ArrayList()
\r
763 for (pathElement : path) {
\r
764 actual.add(pathElement)
\r
765 val DataSchemaNode nodeByPath = childNodes.get(new SchemaPath(actual, absolute))
\r
766 if (!(nodeByPath instanceof ChoiceNode) && !(nodeByPath instanceof ChoiceCaseNode)) {
\r
767 result.append(pathElement.localName)
\r
768 if (i != path.size - 1) {
\r
775 return result.toString
\r
778 private def void collectChildNodes(Collection<DataSchemaNode> source, Map<SchemaPath, DataSchemaNode> destination) {
\r
779 for (node : source) {
\r
780 destination.put(node.path, node)
\r
781 if (node instanceof DataNodeContainer) {
\r
782 collectChildNodes((node as DataNodeContainer).childNodes, destination)
\r
784 if (node instanceof ChoiceNode) {
\r
785 val List<DataSchemaNode> choiceCases = new ArrayList()
\r
786 for (caseNode : (node as ChoiceNode).cases) {
\r
787 choiceCases.add(caseNode)
\r
789 collectChildNodes(choiceCases, destination)
\r
794 private def CharSequence pathToTree(List<QName> path) '''
\r
795 «IF path !== null && !path.empty»
\r
797 «FOR pathElement : path»
\r
799 «pathElement.namespace» «pathElement.localName»
\r
806 private def dispatch addedByInfo(SchemaNode node) '''
\r
809 private def dispatch addedByInfo(DataSchemaNode node) '''
\r
810 «IF node.augmenting»(A)«ENDIF»«IF node.addedByUses»(U)«ENDIF»
\r
813 private def dispatch isAddedBy(SchemaNode node) {
\r
817 private def dispatch isAddedBy(DataSchemaNode node) {
\r
818 if (node.augmenting || node.addedByUses) {
\r
825 private def dispatch nodeName(SchemaNode node) '''
\r
826 «IF node.isAddedBy»
\r
827 «italic(node.QName.localName)»«node.addedByInfo»
\r
829 «node.QName.localName»«node.addedByInfo»
\r
833 private def dispatch nodeName(ContainerSchemaNode node) '''
\r
834 «IF node.isAddedBy»
\r
835 «strong(italic(node.QName.localName))»«node.addedByInfo»
\r
837 «strong(node.QName.localName)»«node.addedByInfo»
\r
841 private def dispatch nodeName(ListSchemaNode node) '''
\r
842 «IF node.isAddedBy»
\r
843 «strong(italic(node.QName.localName))» «IF node.keyDefinition !== null && !node.keyDefinition.empty»«node.listKeys»«ENDIF»«node.addedByInfo»
\r
845 «strong(node.QName.localName)» «IF node.keyDefinition !== null && !node.keyDefinition.empty»«node.listKeys»«ENDIF»
\r