Merge "Bug 447 - Yang documentation generator improvements"
[yangtools.git] / code-generator / maven-sal-api-gen-plugin / src / main / java / org / opendaylight / yangtools / yang / unified / doc / generator / GeneratorImpl.xtend
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.yang.unified.doc.generator
9
10 import org.opendaylight.yangtools.yang.model.api.SchemaContext
11 import java.io.File
12 import java.util.Set
13 import org.opendaylight.yangtools.yang.model.api.Module
14 import java.io.IOException
15 import java.util.HashSet
16 import java.io.BufferedWriter
17 import java.io.OutputStreamWriter;
18 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode
19 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
20 import org.opendaylight.yangtools.yang.model.api.TypeDefinition
21 import org.opendaylight.yangtools.yang.model.api.SchemaNode
22 import org.opendaylight.yangtools.yang.model.util.ExtendedType
23 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition
24 import java.text.SimpleDateFormat
25 import java.util.Collection
26 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint
27 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition
28 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition
29 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint
30 import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition
31 import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition
32 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition
33 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer
34 import org.slf4j.LoggerFactory
35 import org.slf4j.Logger
36 import java.util.List
37 import org.opendaylight.yangtools.yang.common.QName
38 import org.opendaylight.yangtools.yang.model.api.ExtensionDefinition
39 import java.util.ArrayList
40 import java.util.Map
41 import org.opendaylight.yangtools.yang.model.api.SchemaPath
42
43 import org.sonatype.plexus.build.incremental.BuildContext;
44 import org.sonatype.plexus.build.incremental.DefaultBuildContext;
45
46 import org.opendaylight.yangtools.yang.model.api.ChoiceNode
47 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode
48 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode
49 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier
50 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates
51 import java.util.LinkedHashMap
52 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier
53 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode
54 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode\rimport java.util.HashMap
55 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode
56 import org.opendaylight.yangtools.yang.model.api.UsesNode
57 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition
58 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget
59
60 class GeneratorImpl {
61
62     File path
63     static val REVISION_FORMAT = new SimpleDateFormat("yyyy-MM-dd")
64     static val Logger LOG = LoggerFactory.getLogger(GeneratorImpl)
65     static val BuildContext CTX = new DefaultBuildContext();
66     var Module currentModule;
67     val Map<String, String> imports = new HashMap();
68     var SchemaContext ctx;
69
70     def generate(SchemaContext context, File targetPath, Set<Module> modulesToGen) throws IOException {
71         path = targetPath;
72         path.mkdirs();
73         val it = new HashSet;
74         for (module : modulesToGen) {
75             add(generateDocumentation(module, context));
76         }
77         return it;
78     }
79
80     def generateDocumentation(Module module, SchemaContext ctx) {
81         val destination = new File(path, '''«module.name».html''')
82         this.ctx = ctx;
83         module.imports.forEach[importModule | this.imports.put(importModule.prefix, importModule.moduleName)]
84         try {
85             val fw = new OutputStreamWriter(CTX.newFileOutputStream(destination))
86             val bw = new BufferedWriter(fw)
87             currentModule = module;
88             bw.append(generate(module, ctx));
89             bw.close();
90             fw.close();
91         } catch (IOException e) {
92             LOG.error(e.getMessage());
93         }
94         return destination;
95     }
96
97     def generate(Module module, SchemaContext ctx) '''
98         <!DOCTYPE html>
99         <html lang="en">
100           <head>
101             <title>«module.name»</title>
102           </head>
103           <body>
104             «body(module, ctx)»
105           </body>
106         </html>
107     '''
108
109     def body(Module module, SchemaContext ctx) '''
110         «header(module)»
111
112         «typeDefinitionsSummary(module)»
113         «identitiesSummary(module)»
114         «groupingsSummary(module)»
115         «augmentationsSummary(module, ctx)»
116         «objectsSummary(module)»
117         «notificationsSummary(module)»
118         «rpcsSummary(module)»
119         «extensionsSummary(module)»
120         «featuresSummary(module)»
121
122         «typeDefinitions(module)»
123
124         «identities(module)»
125
126         «groupings(module)»
127
128         «dataStore(module)»
129
130         «childNodes(module)»
131
132         «notifications(module)»
133
134         «augmentations(module, ctx)»
135
136         «rpcs(module)»
137
138         «extensions(module)»
139
140         «features(module)»
141
142     '''
143
144
145     private def typeDefinitionsSummary(Module module) {
146         val Set<TypeDefinition<?>> typedefs = module.typeDefinitions
147         if (typedefs.empty) {
148             return '';
149         }
150         return '''
151         <div>
152             <h3>Type Definitions Summary</h3>
153             <table>
154                 <tr>
155                     <th>Name</th>
156                     <th>Description</th>
157                 </tr>
158                 «FOR typedef : typedefs»
159                 <tr>
160                     <td>
161                     «anchorLink(typedef.QName.localName, strong(typedef.QName.localName))»
162                     </td>
163                     <td>
164                     «typedef.description»
165                     </td>
166                 </tr>
167                 «ENDFOR»
168             </table>
169         </div>
170         '''
171     }
172
173     def typeDefinitions(Module module) {
174         val Set<TypeDefinition<?>> typedefs = module.typeDefinitions
175         if (typedefs.empty) {
176             return '';
177         }
178         return '''
179             <h2>Type Definitions</h2>
180             <ul>
181             «FOR typedef : typedefs»
182                 <li>
183                     <h3 id="«typedef.QName.localName»">«typedef.QName.localName»</h3>
184                     <ul>
185                     «typedef.descAndRefLi»
186                     «typedef.restrictions»
187                     </ul>
188                 </li>
189             «ENDFOR»
190             </ul>
191         '''
192     }
193
194     private def identities(Module module) {
195         if (module.identities.empty) {
196             return '';
197         }
198         return '''
199             <h2>Identities</h2>
200             <ul>
201             «FOR identity : module.identities»
202                 <li>
203                     <h3 id="«identity.QName.localName»">«identity.QName.localName»</h3>
204                     <ul>
205                     «identity.descAndRefLi»
206                     «IF identity.baseIdentity !== null»
207                         «listItem("base", identity.baseIdentity.QName.localName)»
208                     «ENDIF»
209                     </ul>
210                 </li>
211             «ENDFOR»
212             </ul>
213         '''
214     }
215
216     private def identitiesSummary(Module module) {
217         if (module.identities.empty) {
218             return '';
219         }
220         return '''
221         <h3>Identities Summary</h3>
222         <table>
223             <tr>
224                 <th>Name</th>
225                 <th>Description</th>
226             </tr>
227             «FOR identity : module.identities»
228             <tr>
229                 <td>
230                 «anchorLink(identity.QName.localName, strong(identity.QName.localName))»
231                 </td>
232                 <td>
233                 «identity.description»
234                 </td>
235             </tr>
236             «ENDFOR»
237         </table>
238         '''
239     }
240
241     private def groupings(Module module) {
242         if (module.groupings.empty) {
243             return '';
244         }
245         return '''
246             <h2>Groupings</h2>
247             <ul>
248             «FOR grouping : module.groupings»
249                 <li>
250                     <h3 id="«grouping.QName.localName»">«grouping.QName.localName»</h3>
251                     <ul>
252                         «grouping.descAndRefLi»
253                         «FOR childNode : grouping.childNodes»
254                             «childNode.printSchemaNodeInfo»
255                         «ENDFOR»
256                     </ul>
257                 </li>
258             «ENDFOR»
259             </ul>
260         '''
261     }
262
263     private def groupingsSummary(Module module) {
264         if (module.groupings.empty) {
265             return '';
266         }
267         return '''
268         <h3>Groupings Summary</h3>
269         <table>
270             <tr>
271                 <th>Name</th>
272                 <th>Description</th>
273             </tr>
274             «FOR grouping : module.groupings»
275             <tr>
276                 <td>
277                 «anchorLink(grouping.QName.localName, strong(grouping.QName.localName))»
278                 </td>
279                 <td>
280                 «grouping.description»
281                 </td>
282             </tr>
283             «ENDFOR»
284         </table>
285         '''
286     }
287
288     def dataStore(Module module) {
289         if (module.childNodes.empty) {
290             return '';
291         }
292         return '''
293             <h2>Datastore Structure</h2>
294             «tree(module)»
295         '''
296     }
297
298     def augmentations(Module module, SchemaContext context) {
299         if (module.augmentations.empty) {
300             return '';
301         }
302         return '''
303             <h2>Augmentations</h2>
304
305             <ul>
306             «FOR augment : module.augmentations»
307                 <li>
308                     <h3 id="«schemaPathToString(module, augment.targetPath, context, augment)»">
309                     Target [«typeAnchorLink(augment.targetPath,schemaPathToString(module, augment.targetPath, context, augment))»]</h3>
310                     «augment.description»
311                     «IF augment.reference !== null»
312                         Reference «augment.reference»
313                     «ENDIF»
314                     «IF augment.whenCondition !== null»
315                         When «augment.whenCondition.toString»
316                     «ENDIF»
317                     «FOR childNode : augment.childNodes»
318                         «childNode.printSchemaNodeInfo»
319                     «ENDFOR»
320                 </li>
321             «ENDFOR»
322             </ul>
323         '''
324     }
325
326     private def augmentationsSummary(Module module, SchemaContext context) {
327         if (module.augmentations.empty) {
328             return '';
329         }
330         return '''
331         <h3>Augmentations Summary</h3>
332         <table>
333             <tr>
334                 <th>Target</th>
335                 <th>Description</th>
336             </tr>
337             «FOR augment : module.augmentations»
338             <tr>
339                 <td>
340                 «anchorLink(schemaPathToString(module, augment.targetPath, context, augment),
341                 strong(schemaPathToString(module, augment.targetPath, context, augment)))»
342                 </td>
343                 <td>
344                 «augment.description»
345                 </td>
346             </tr>
347             «ENDFOR»
348         </table>
349         '''
350     }
351
352     def notifications(Module module) {
353         val Set<NotificationDefinition> notificationdefs = module.notifications
354         if (notificationdefs.empty) {
355             return '';
356         }
357
358         return '''
359             <h2>Notifications</h2>
360             «FOR notificationdef : notificationdefs»
361
362                 <h3 id="«notificationdef.path.schemaPathToId»">«notificationdef.nodeName»</h3>
363                     «notificationdef.descAndRef»
364                     «FOR childNode : notificationdef.childNodes»
365                         «childNode.printSchemaNodeInfo»
366                     «ENDFOR»
367             «ENDFOR»
368         '''
369     }
370
371     private def notificationsSummary(Module module) {
372         if (module.notifications.empty) {
373             return '';
374         }
375         return '''
376         <h3>Notifications Summary</h3>
377         <table>
378             <tr>
379                 <th>Name</th>
380                 <th>Description</th>
381             </tr>
382             «FOR notification : module.notifications»
383             <tr>
384                 <td>
385                 «anchorLink(notification.path.schemaPathToId, strong(notification.QName.localName))»
386                 </td>
387                 <td>
388                 «notification.description»
389                 </td>
390             </tr>
391             «ENDFOR»
392         </table>
393         '''
394     }
395
396     def rpcs(Module module) {
397         if (module.rpcs.empty) {
398             return '';
399         }
400
401         return '''
402             <h2>RPC Definitions</h2>
403             «FOR rpc : module.rpcs»
404                 <h3 id="«rpc.QName.localName»">«rpc.nodeName»</h3>
405                     <ul>
406                         «rpc.descAndRefLi»
407                         «rpc.input.printSchemaNodeInfo»
408                         «rpc.output.printSchemaNodeInfo»
409                     </ul>
410             «ENDFOR»
411             </ul>
412         '''
413     }
414
415     private def rpcsSummary(Module module) {
416         if (module.rpcs.empty) {
417             return '';
418         }
419         return '''
420         <h3>RPCs Summary</h3>
421         <table>
422             <tr>
423                 <th>Name</th>
424                 <th>Description</th>
425             </tr>
426             «FOR rpc : module.rpcs»
427             <tr>
428                 <td>
429                 «anchorLink(rpc.QName.localName, strong(rpc.QName.localName))»
430                 </td>
431                 <td>
432                 «rpc.description»
433                 </td>
434             </tr>
435             «ENDFOR»
436         </table>
437         '''
438     }
439
440     def extensions(Module module) {
441         if (module.extensionSchemaNodes.empty) {
442             return '';
443         }
444         return '''
445             <h2>Extensions</h2>
446             «FOR ext : module.extensionSchemaNodes»
447                 <li>
448                     <h3 id="«ext.QName.localName»">«ext.nodeName»</h3>
449                 </li>
450                 «extensionInfo(ext)»
451             «ENDFOR»
452         '''
453     }
454
455     private def extensionsSummary(Module module) {
456         if (module.extensionSchemaNodes.empty) {
457             return '';
458         }
459         return '''
460         <h3>Extensions Summary</h3>
461         <table>
462             <tr>
463                 <th>Name</th>
464                 <th>Description</th>
465             </tr>
466             «FOR ext : module.extensionSchemaNodes»
467             <tr>
468                 <td>
469                 «anchorLink(ext.QName.localName, strong(ext.QName.localName))»
470                 </td>
471                 <td>
472                 «ext.description»
473                 </td>
474             </tr>
475             «ENDFOR»
476         </table>
477         '''
478     }
479
480     def features(Module module) {
481         if (module.features.empty) {
482             return '';
483         }
484         return '''
485             <h2>Features</h2>
486
487             <ul>
488             «FOR feature : module.features»
489                 <li>
490                     <h3 id="«feature.QName.localName»">«feature.QName.localName»</h3>
491                     <ul>
492                         «feature.descAndRefLi»
493                     </ul>
494                 </li>
495             «ENDFOR»
496             </ul>
497         '''
498     }
499
500     private def featuresSummary(Module module) {
501         if (module.features.empty) {
502             return '';
503         }
504         return '''
505         <h3>Features Summary</h3>
506         <table>
507             <tr>
508                 <th>Name</th>
509                 <th>Description</th>
510             </tr>
511             «FOR feature : module.features»
512             <tr>
513                 <td>
514                 «anchorLink(feature.QName.localName, strong(feature.QName.localName))»
515                 </td>
516                 <td>
517                 «feature.description»
518                 </td>
519             </tr>
520             «ENDFOR»
521         </table>
522         '''
523     }
524
525     private def objectsSummary(Module module) {
526         if (module.childNodes.empty) {
527             return '';
528         }
529         return '''
530         <h3>Child Nodes Summary</h3>
531         <table>
532             <tr>
533                 <th>Name</th>
534                 <th>Description</th>
535             </tr>
536             «FOR childNode : module.childNodes»
537             <tr>
538                 <td>
539                 «anchorLink(childNode.QName.localName, strong(childNode.QName.localName))»
540                 </td>
541                 <td>
542                 «childNode.description»
543                 </td>
544             </tr>
545             «ENDFOR»
546         </table>
547         '''
548     }
549
550     def header(Module module)
551     '''
552         <h1>«module.name»</h1>
553
554         <h2>Base Information</h2>
555         <table>
556             <tr>
557                 <td>«strong("prefix")»</td>
558                 <td>«module.prefix»</td>
559             </tr>
560             <tr>
561                 <td>«strong("namespace")»</td>
562                 <td>«module.namespace»</td>
563             </tr>
564             <tr>
565                 <td>«strong("revision")»</td>
566                 <td>«REVISION_FORMAT.format(module.revision)»</td>
567             </tr>
568             <tr>
569                 <td>«strong("description")»</td>
570                 <td>«module.description»</td>
571             </tr>
572             <tr>
573                 <td>«strong("yang-version")»</td>
574                 <td>«module.yangVersion»</td>
575             </tr>
576             <tr>
577                 «FOR imp : module.imports BEFORE '''<td>«strong("imports")»</td><td>''' AFTER '''</td>'''»
578                     «imp.prefix»:«imp.moduleName»«IF imp.revision !== null» «REVISION_FORMAT.format(imp.revision)»«ENDIF»;
579                 «ENDFOR»
580             </tr>
581         </table>
582     '''
583
584     def CharSequence schemaPathToId(SchemaPath path) {
585         if(path !== null) {
586             return '''«FOR qName : path.path SEPARATOR "/"»«qName.localName»«ENDFOR»'''
587         }
588     }
589
590     def code(String string) '''<code>«string»</code>'''
591
592     def process(Module module) {
593         throw new UnsupportedOperationException("TODO: auto-generated method stub")
594     }
595
596     def CharSequence tree(Module module) '''
597         «strong(module.name)»
598         «module.childNodes.treeSet(InstanceIdentifier.builder.toInstance())»
599     '''
600
601     private def dispatch CharSequence tree(ChoiceNode node,InstanceIdentifier path) '''
602         «node.nodeName» (choice)
603         «casesTree(node.cases,path)»
604     '''
605
606     def casesTree(Set<ChoiceCaseNode> nodes,InstanceIdentifier path) '''
607         <ul>
608         «FOR node : nodes»
609             <li>
610             «node.nodeName»
611             «node.childNodes.treeSet(path)»
612             </li>
613         «ENDFOR»
614         </ul>
615     '''
616
617     private def dispatch CharSequence tree(DataSchemaNode node,InstanceIdentifier path) '''
618         «node.nodeName»
619     '''
620
621     private def dispatch CharSequence tree(ListSchemaNode node,InstanceIdentifier path) '''
622         «val newPath = path.append(node)»
623         «localLink(newPath,node.nodeName)»
624         «node.childNodes.treeSet(newPath)»
625     '''
626
627     private def dispatch CharSequence tree(ContainerSchemaNode node,InstanceIdentifier path) '''
628         «val newPath = path.append(node)»
629         «localLink(newPath,node.nodeName)»
630         «node.childNodes.treeSet(newPath)»
631     '''
632
633     def CharSequence childNodes(Module module) '''
634         «val childNodes = module.childNodes»
635         «IF !childNodes.nullOrEmpty»
636             <h2>Child nodes</h2>
637
638             «childNodes.printChildren(3,InstanceIdentifier.builder().toInstance())»
639         «ENDIF»
640     '''
641
642     def CharSequence printSchemaNodeInfo(DataSchemaNode node) {
643         return '''
644             <ul>
645             «node.printBaseInfo»
646             «IF node instanceof DataNodeContainer»
647                 «val dataNode = node as DataNodeContainer»
648                 <ul>
649                 «FOR usesNode : dataNode.uses»
650                     «usesNode.printUses»
651                 «ENDFOR»
652                 </ul>
653                 <ul>
654                 «FOR typeDef : dataNode.typeDefinitions»
655                     «typeDef.restrictions»
656                 «ENDFOR»
657                 </ul>
658                 <ul>
659                 «FOR grouping : dataNode.groupings»
660                     «grouping.printGrouping»
661                 «ENDFOR»
662                 </ul>
663                 <ul>
664                 «FOR child : dataNode.childNodes»
665                     «child.printSchemaNodeInfo»
666                 «ENDFOR»
667                 </ul>
668             «ENDIF»
669             </ul>
670         '''
671     }
672
673     def String typeAnchorLink(SchemaPath path, CharSequence text) {
674         if(path !== null) {
675             val prefix = path.path.last.prefix
676             if(prefix == this.currentModule.prefix) {
677                 return '''<a href="#«path.schemaPathToId»">«text»</a>'''
678             } else if(!prefix.nullOrEmpty){
679                 val String module = imports.get(prefix)
680                 if(!module.nullOrEmpty) {
681                     return '''«prefix»:«text»'''
682                     //to enable external (import) links
683                     //return '''<a href="«module».html#«path.schemaPathToId»">«prefix»:«text»</a>'''
684                 }
685             }
686             return text.toString
687         }
688     }
689
690     def CharSequence printBaseInfo(SchemaNode node) {
691         if(node instanceof LeafSchemaNode) {
692             val LeafSchemaNode leafNode = (node as LeafSchemaNode)
693             return '''
694                 «printInfo(node, "leaf")»
695                 «listItem("type", typeAnchorLink(leafNode.type?.path, leafNode.type.QName.localName))»
696                 «listItem("units", leafNode.units)»
697                 «listItem("default", leafNode.^default)»
698                 </ul>
699             '''
700         } else if(node instanceof LeafListSchemaNode) {
701             val LeafListSchemaNode leafListNode = (node as LeafListSchemaNode)
702             return '''
703                 «printInfo(node, "leaf-list")»
704                 «listItem("type", leafListNode.type?.QName.localName)»
705                 </ul>
706             '''
707         } else if(node instanceof ListSchemaNode) {
708             val ListSchemaNode listNode = (node as ListSchemaNode)
709             return '''
710                 «printInfo(node, "list")»
711                 «FOR keyDef : listNode.keyDefinition»
712                     «listItem("key definition", keyDef.localName)»
713                 «ENDFOR»
714                 </ul>
715             '''
716         } else if(node instanceof ChoiceNode) {
717             val ChoiceNode choiceNode = (node as ChoiceNode)
718             return '''
719                 «printInfo(node, "choice")»
720                 «listItem("default case", choiceNode.defaultCase)»
721                 «FOR caseNode : choiceNode.cases»
722                     «caseNode.printSchemaNodeInfo»
723                 «ENDFOR»
724                 </ul>
725             '''
726         } else if(node instanceof ChoiceCaseNode) {
727             return '''
728                 «printInfo(node, "case")»
729                 </ul>
730             '''
731         } else if(node instanceof ContainerSchemaNode) {
732             return '''
733                 «printInfo(node, "container")»
734                 </ul>
735             '''
736         } else if(node instanceof AnyXmlSchemaNode) {
737             return '''
738                 «printInfo(node, "anyxml")»
739                 </ul>
740             '''
741         }
742     }
743
744     def CharSequence printInfo(SchemaNode node, String nodeType) {
745         return '''
746             «IF node instanceof AugmentationTarget»
747                 «IF node !== null»
748                     <strong>
749                     <li id="«node.path.schemaPathToId»">
750                         «nodeType»: «node.QName.localName»
751                     </li>
752                     </strong>
753                 «ENDIF»
754             «ELSE»
755                 «strong(listItem(nodeType, node.QName.localName))»
756             «ENDIF»
757             <ul>
758             «listItem("description", node.description)»
759             «listItem("reference", node.reference)»
760             «IF node instanceof DataSchemaNode»
761                 «listItem("when condition", (node as DataSchemaNode).constraints.whenCondition?.toString)»
762                 «listItem("min elements", (node as DataSchemaNode).constraints.minElements?.toString)»
763                 «listItem("max elements", (node as DataSchemaNode).constraints.maxElements?.toString)»
764             «ENDIF»
765         '''
766     }
767
768     def CharSequence printUses(UsesNode usesNode) {
769         return '''
770             «strong(listItem("uses", typeAnchorLink(usesNode.groupingPath, usesNode.groupingPath.path.last.localName)))»
771             <ul>
772             <li>refines:
773                 <ul>
774                 «FOR sp : usesNode.refines.keySet»
775                     «listItem("node name", usesNode.refines.get(sp).QName.localName)»
776                 «ENDFOR»
777                 </ul>
778             </li>
779             «FOR augment : usesNode.augmentations»
780                 «typeAnchorLink(augment.targetPath,schemaPathToString(currentModule, augment.targetPath, ctx, augment))»
781             «ENDFOR»
782             </ul>
783         '''
784     }
785
786     def CharSequence printGrouping(GroupingDefinition grouping) {
787         return '''
788             «strong(listItem("grouping", grouping.QName.localName))»
789         '''
790     }
791
792     def CharSequence printChildren(Set<DataSchemaNode> nodes, int level, InstanceIdentifier path) {
793         val anyxmlNodes = nodes.filter(AnyXmlSchemaNode)
794         val leafNodes = nodes.filter(LeafSchemaNode)
795         val leafListNodes = nodes.filter(LeafListSchemaNode)
796         val choices = nodes.filter(ChoiceNode)
797         val cases = nodes.filter(ChoiceCaseNode)
798         val containers = nodes.filter(ContainerSchemaNode)
799         val lists = nodes.filter(ListSchemaNode)
800         return '''
801             «IF ((anyxmlNodes.size + leafNodes.size + leafListNodes.size + containers.size + lists.size) > 0)»
802             <h3>Direct children</h3>
803             <ul>
804             «FOR childNode : anyxmlNodes»
805                 «childNode.printShortInfo(level,path)»
806             «ENDFOR»
807             «FOR childNode : leafNodes»
808                 «childNode.printShortInfo(level,path)»
809             «ENDFOR»
810             «FOR childNode : leafListNodes»
811                 «childNode.printShortInfo(level,path)»
812             «ENDFOR»
813             «FOR childNode : containers»
814                 «childNode.printShortInfo(level,path)»
815             «ENDFOR»
816             «FOR childNode : lists»
817                 «childNode.printShortInfo(level,path)»
818             «ENDFOR»
819             </ul>
820             «ENDIF»
821
822             «IF !path.path.empty»
823             <h3>XML example</h3>
824             «nodes.xmlExample(path.path.last.nodeType,path)»
825             </h3>
826             «ENDIF»
827             «FOR childNode : containers»
828                 «childNode.printInfo(level,path)»
829             «ENDFOR»
830             «FOR childNode : lists»
831                 «childNode.printInfo(level,path)»
832             «ENDFOR»
833             «FOR childNode : choices»
834                 «childNode.printInfo(level,path)»
835             «ENDFOR»
836             «FOR childNode : cases»
837                 «childNode.printInfo(level,path)»
838             «ENDFOR»
839         '''
840     }
841
842     def CharSequence xmlExample(Set<DataSchemaNode> nodes, QName name,InstanceIdentifier path) '''
843     <pre>
844         «xmlExampleTag(name,nodes.xmplExampleTags(path))»
845     </pre>
846     '''
847
848     def CharSequence xmplExampleTags(Set<DataSchemaNode> nodes, InstanceIdentifier identifier) '''
849         <!-- Child nodes -->
850         «FOR node : nodes»
851         <!-- «node.QName.localName» -->
852             «node.asXmlExampleTag(identifier)»
853         «ENDFOR»
854
855     '''
856
857     private def dispatch CharSequence asXmlExampleTag(LeafSchemaNode node, InstanceIdentifier identifier) '''
858         «node.QName.xmlExampleTag("...")»
859     '''
860
861     private def dispatch CharSequence asXmlExampleTag(LeafListSchemaNode node, InstanceIdentifier identifier) '''
862         &lt!-- This node could appear multiple times --&gt
863         «node.QName.xmlExampleTag("...")»
864     '''
865
866     private def dispatch CharSequence asXmlExampleTag(ContainerSchemaNode node, InstanceIdentifier identifier) '''
867         &lt!-- See «localLink(identifier.append(node),"definition")» for child nodes.  --&gt
868         «node.QName.xmlExampleTag("...")»
869     '''
870
871
872     private def dispatch CharSequence asXmlExampleTag(ListSchemaNode node, InstanceIdentifier identifier) '''
873         &lt!-- See «localLink(identifier.append(node),"definition")» for child nodes.  --&gt
874         &lt!-- This node could appear multiple times --&gt
875         «node.QName.xmlExampleTag("...")»
876     '''
877
878
879     private def dispatch CharSequence asXmlExampleTag(DataSchemaNode node, InstanceIdentifier identifier) '''
880         <!-- noop -->
881     '''
882
883
884     def xmlExampleTag(QName name, CharSequence data) {
885         return '''&lt;«name.localName» xmlns="«name.namespace»"&gt;«data»&lt;/«name.localName»&gt;'''
886     }
887
888     def header(int level,QName name) '''<h«level»>«name.localName»</h«level»>'''
889
890
891     def header(int level,InstanceIdentifier name) '''
892         <h«level» id="«FOR cmp : name.path SEPARATOR "/"»«cmp.nodeType.localName»«ENDFOR»">
893             «FOR cmp : name.path SEPARATOR "/"»«cmp.nodeType.localName»«ENDFOR»
894         </h«level»>
895     '''
896
897
898
899     private def dispatch CharSequence printInfo(DataSchemaNode node, int level, InstanceIdentifier path) '''
900         «header(level+1,node.QName)»
901     '''
902
903     private def dispatch CharSequence printInfo(ContainerSchemaNode node, int level, InstanceIdentifier path) '''
904         «val newPath = path.append(node)»
905         «header(level,newPath)»
906         <dl>
907           <dt>XML Path</dt>
908           <dd>«newPath.asXmlPath»</dd>
909           <dt>Restconf path</dt>
910           <dd>«code(newPath.asRestconfPath)»</dd>
911         </dl>
912         «node.childNodes.printChildren(level,newPath)»
913     '''
914
915     private def dispatch CharSequence printInfo(ListSchemaNode node, int level, InstanceIdentifier path) '''
916         «val newPath = path.append(node)»
917         «header(level,newPath)»
918         <dl>
919           <dt>XML Path</dt>
920           <dd>«newPath.asXmlPath»</dd>
921           <dt>Restconf path</dt>
922           <dd>«code(newPath.asRestconfPath)»</dd>
923         </dl>
924         «node.childNodes.printChildren(level,newPath)»
925     '''
926
927     private def dispatch CharSequence printInfo(ChoiceNode node, int level, InstanceIdentifier path) '''
928         «val Set<DataSchemaNode> choiceCases = new HashSet(node.cases)»
929         «choiceCases.printChildren(level,path)»
930     '''
931
932     private def dispatch CharSequence printInfo(ChoiceCaseNode node, int level, InstanceIdentifier path) '''
933         «node.childNodes.printChildren(level,path)»
934     '''
935
936
937
938     def CharSequence printShortInfo(ContainerSchemaNode node, int level, InstanceIdentifier path) {
939         val newPath = path.append(node);
940         return '''
941             <li>«strong(localLink(newPath,node.QName.localName))» (container)</li>
942         '''
943     }
944
945     def CharSequence printShortInfo(ListSchemaNode node, int level, InstanceIdentifier path) {
946         val newPath = path.append(node);
947         return '''
948             <li>«strong(localLink(newPath,node.QName.localName))» (list)</li>
949         '''
950     }
951
952     def CharSequence printShortInfo(AnyXmlSchemaNode node, int level, InstanceIdentifier path) {
953         return '''
954             <li>«strong((node.QName.localName))» (anyxml)</li>
955         '''
956     }
957
958     def CharSequence printShortInfo(LeafSchemaNode node, int level, InstanceIdentifier path) {
959         return '''
960             <li>«strong((node.QName.localName))» (leaf)</li>
961         '''
962     }
963
964     def CharSequence printShortInfo(LeafListSchemaNode node, int level, InstanceIdentifier path) {
965         return '''
966             <li>«strong((node.QName.localName))» (leaf-list)</li>
967         '''
968     }
969
970     def CharSequence anchorLink(CharSequence anchor, CharSequence text) {
971         return '''
972             <a href="#«anchor»">«text»</a>
973         '''
974     }
975
976     def CharSequence localLink(InstanceIdentifier identifier, CharSequence text) '''
977         <a href="#«FOR cmp : identifier.path SEPARATOR "/"»«cmp.nodeType.localName»«ENDFOR»">«text»</a>
978     '''
979
980
981     private def dispatch InstanceIdentifier append(InstanceIdentifier identifier, ContainerSchemaNode node) {
982         val pathArguments = new ArrayList(identifier.path)
983         pathArguments.add(new NodeIdentifier(node.QName));
984         return new InstanceIdentifier(pathArguments);
985     }
986
987     private def dispatch InstanceIdentifier append(InstanceIdentifier identifier, ListSchemaNode node) {
988         val pathArguments = new ArrayList(identifier.path)
989         val keyValues = new LinkedHashMap<QName,Object>();
990         if(node.keyDefinition !== null) {
991             for(definition : node.keyDefinition) {
992                 keyValues.put(definition,new Object);
993             }
994         }
995         pathArguments.add(new NodeIdentifierWithPredicates(node.QName,keyValues));
996         return new InstanceIdentifier(pathArguments);
997     }
998
999
1000     def asXmlPath(InstanceIdentifier identifier) {
1001         return "";
1002     }
1003
1004     def asRestconfPath(InstanceIdentifier identifier) {
1005         val it = new StringBuilder();
1006         append(currentModule.name)
1007         append(":")
1008         var previous = false;
1009         for(arg : identifier.path) {
1010             if(previous) append("/")
1011             append(arg.nodeType.localName);
1012             previous = true;
1013             if(arg instanceof NodeIdentifierWithPredicates) {
1014                 val nodeIdentifier = arg as NodeIdentifierWithPredicates;
1015                 for(qname : nodeIdentifier.keyValues.keySet) {
1016                     append("/{");
1017                     append(qname.localName)
1018                     append("}")
1019                 }
1020             }
1021         }
1022
1023         return it.toString;
1024     }
1025
1026     private def String schemaPathToString(Module module, SchemaPath schemaPath, SchemaContext ctx, DataNodeContainer dataNode) {
1027             val List<QName> path = schemaPath.path
1028         val StringBuilder pathString = new StringBuilder()
1029         if (schemaPath.absolute) {
1030             pathString.append("/")
1031         }
1032
1033         val QName qname = path.get(0)
1034         var Object parent = ctx.findModuleByNamespaceAndRevision(qname.namespace, qname.revision)
1035
1036         for (name : path) {
1037             if (parent instanceof DataNodeContainer) {
1038                 var SchemaNode node = (parent as DataNodeContainer).getDataChildByName(name)
1039                 if (node == null && (parent instanceof Module)) {
1040                     val notifications = (parent as Module).notifications;
1041                     for (notification : notifications) {
1042                         if (notification.QName.equals(name)) {
1043                             node = notification
1044                         }
1045                     }
1046                 }
1047                 if (node == null && (parent instanceof Module)) {
1048                     val rpcs = (parent as Module).rpcs;
1049                     for (rpc : rpcs) {
1050                         if (rpc.QName.equals(name)) {
1051                             node = rpc
1052                         }
1053                     }
1054                 }
1055
1056                 var String prefix = name.prefix
1057                 var String moduleName
1058                 if (prefix == null || "".equals(prefix) || prefix.equals(module.prefix)) {
1059                     moduleName = module.name
1060                 } else {
1061                     moduleName = imports.get(prefix)
1062                 }
1063                 pathString.append(moduleName)
1064                 pathString.append(":")
1065                 pathString.append(name.localName)
1066                 pathString.append("/")
1067                 if(node instanceof ChoiceNode && dataNode !== null) {
1068                     val DataSchemaNode caseNode = dataNode.childNodes.findFirst[DataSchemaNode e | e instanceof ChoiceCaseNode];
1069                     if(caseNode !== null) {
1070                         pathString.append("(case)");
1071                         pathString.append(caseNode.QName.localName);
1072                     }
1073                 }
1074                 parent = node
1075             }
1076         }
1077         return pathString.toString;
1078     }
1079
1080
1081     def CharSequence childNodesInfoTree(Map<SchemaPath, DataSchemaNode> childNodes) '''
1082         «IF childNodes !== null && !childNodes.empty»
1083             «FOR child : childNodes.values»
1084                 «childInfo(child, childNodes)»
1085             «ENDFOR»
1086         «ENDIF»
1087     '''
1088
1089     def CharSequence childInfo(DataSchemaNode node, Map<SchemaPath, DataSchemaNode> childNodes) '''
1090         «val String path = nodeSchemaPathToPath(node, childNodes)»
1091         «IF path != null»
1092             «code(path)»
1093                 «IF node !== null»
1094                 <ul>
1095                 «node.descAndRefLi»
1096                 </ul>
1097             «ENDIF»
1098         «ENDIF»
1099     '''
1100
1101     private def CharSequence treeSet(Collection<DataSchemaNode> childNodes, InstanceIdentifier path) '''
1102         «IF childNodes !== null && !childNodes.empty»
1103             <ul>
1104             «FOR child : childNodes»
1105                 <li>
1106                     «child.tree(path)»
1107                 </li>
1108             «ENDFOR»
1109             </ul>
1110         «ENDIF»
1111     '''
1112
1113     def listKeys(ListSchemaNode node) '''
1114         [«FOR key : node.keyDefinition SEPARATOR " "»«key.localName»«ENDFOR»]
1115     '''
1116
1117     private def CharSequence extensionInfo(ExtensionDefinition ext) '''
1118         <ul>
1119             «ext.descAndRefLi»
1120             «listItem("Argument", ext.argument)»
1121         </ul>
1122     '''
1123
1124     private def dispatch CharSequence tree(Void obj, InstanceIdentifier path) '''
1125     '''
1126
1127
1128
1129     /* #################### RESTRICTIONS #################### */
1130     private def restrictions(TypeDefinition<?> type) '''
1131         «type.baseType.toBaseStmt»
1132         «type.toLength»
1133         «type.toRange»
1134     '''
1135
1136     private def dispatch toLength(TypeDefinition<?> type) {
1137     }
1138
1139     private def dispatch toLength(BinaryTypeDefinition type) '''
1140         «type.lengthConstraints.toLengthStmt»
1141     '''
1142
1143     private def dispatch toLength(StringTypeDefinition type) '''
1144         «type.lengthConstraints.toLengthStmt»
1145     '''
1146
1147     private def dispatch toLength(ExtendedType type) '''
1148         «type.lengthConstraints.toLengthStmt»
1149     '''
1150
1151     private def dispatch toRange(TypeDefinition<?> type) {
1152     }
1153
1154     private def dispatch toRange(DecimalTypeDefinition type) '''
1155         «type.rangeConstraints.toRangeStmt»
1156     '''
1157
1158     private def dispatch toRange(IntegerTypeDefinition type) '''
1159         «type.rangeConstraints.toRangeStmt»
1160     '''
1161
1162     private def dispatch toRange(UnsignedIntegerTypeDefinition type) '''
1163         «type.rangeConstraints.toRangeStmt»
1164     '''
1165
1166     private def dispatch toRange(ExtendedType type) '''
1167         «type.rangeConstraints.toRangeStmt»
1168     '''
1169
1170     def toLengthStmt(Collection<LengthConstraint> lengths) '''
1171         «IF lengths != null && !lengths.empty»
1172             «listItem("Length restrictions:")»
1173             <ul>
1174             «FOR length : lengths»
1175                 <li>
1176                 «IF length.min == length.max»
1177                     «length.min»
1178                 «ELSE»
1179                     &lt;«length.min», «length.max»&gt;
1180                 «ENDIF»
1181                 </li>
1182             «ENDFOR»
1183             </ul>
1184         «ENDIF»
1185     '''
1186
1187     def toRangeStmt(Collection<RangeConstraint> ranges) '''
1188         «IF ranges != null && !ranges.empty»
1189             «listItem("Range restrictions:")»
1190             <ul>
1191             «FOR range : ranges»
1192                 <li>
1193                 «IF range.min == range.max»
1194                     «range.min»
1195                 «ELSE»
1196                     &lt;«range.min», «range.max»&gt;
1197                 «ENDIF»
1198                 </li>
1199             «ENDFOR»
1200             </ul>
1201         «ENDIF»
1202     '''
1203
1204     def toBaseStmt(TypeDefinition<?> baseType) '''
1205         «IF baseType != null»
1206         «listItem("Base type", typeAnchorLink(baseType?.path, baseType.QName.localName))»
1207         «ENDIF»
1208     '''
1209
1210
1211
1212     /* #################### UTILITY #################### */
1213     private def String strong(CharSequence str) '''<strong>«str»</strong>'''
1214     private def italic(CharSequence str) '''<i>«str»</i>'''
1215
1216     def CharSequence descAndRefLi(SchemaNode node) '''
1217         «listItem("Description", node.description)»
1218         «listItem("Reference", node.reference)»
1219     '''
1220
1221     def CharSequence descAndRef(SchemaNode node) '''
1222         «node.description»
1223         «IF node.reference !== null»
1224             Reference «node.reference»
1225         «ENDIF»
1226     '''
1227
1228     private def listItem(String value) '''
1229         «IF value !== null && !value.empty»
1230             <li>
1231                 «value»
1232             </li>
1233         «ENDIF»
1234     '''
1235
1236     private def listItem(String name, String value) '''
1237         «IF value !== null && !value.empty»
1238             <li>
1239                 «name»: «value»
1240             </li>
1241         «ENDIF»
1242     '''
1243
1244     private def String nodeSchemaPathToPath(DataSchemaNode node, Map<SchemaPath, DataSchemaNode> childNodes) {
1245         if (node instanceof ChoiceNode || node instanceof ChoiceCaseNode) {
1246             return null
1247         }
1248
1249         val path = node.path.path
1250         val absolute = node.path.absolute;
1251         var StringBuilder result = new StringBuilder
1252         if (absolute) {
1253             result.append("/")
1254         }
1255         if (path !== null && !path.empty) {
1256             val List<QName> actual = new ArrayList()
1257             var i = 0;
1258             for (pathElement : path) {
1259                 actual.add(pathElement)
1260                 val DataSchemaNode nodeByPath = childNodes.get(new SchemaPath(actual, absolute)) 
1261                 if (!(nodeByPath instanceof ChoiceNode) && !(nodeByPath instanceof ChoiceCaseNode)) {
1262                     result.append(pathElement.localName)
1263                     if (i != path.size - 1) {
1264                         result.append("/")
1265                     }
1266                 }
1267                 i = i + 1
1268             }
1269         }
1270         return result.toString
1271     }
1272
1273     private def void collectChildNodes(Collection<DataSchemaNode> source, Map<SchemaPath, DataSchemaNode> destination) {
1274         for (node : source) {
1275             destination.put(node.path, node)
1276             if (node instanceof DataNodeContainer) {
1277                 collectChildNodes((node as DataNodeContainer).childNodes, destination)
1278             }
1279             if (node instanceof ChoiceNode) {
1280                 val List<DataSchemaNode> choiceCases = new ArrayList()
1281                 for (caseNode : (node as ChoiceNode).cases) {
1282                     choiceCases.add(caseNode)
1283                 }
1284                 collectChildNodes(choiceCases, destination)
1285             }
1286         }
1287     }
1288
1289     private def dispatch addedByInfo(SchemaNode node) '''
1290     '''
1291
1292     private def dispatch addedByInfo(DataSchemaNode node) '''
1293         «IF node.augmenting»(A)«ENDIF»«IF node.addedByUses»(U)«ENDIF»
1294     '''
1295
1296     private def dispatch isAddedBy(SchemaNode node) {
1297         return false;
1298     }
1299
1300     private def dispatch isAddedBy(DataSchemaNode node) {
1301         if (node.augmenting || node.addedByUses) {
1302             return true
1303         } else {
1304             return false;
1305         }
1306     }
1307
1308     private def dispatch nodeName(SchemaNode node) '''
1309         «IF node.isAddedBy»
1310             «italic(node.QName.localName)»«node.addedByInfo»
1311         «ELSE»
1312             «node.QName.localName»«node.addedByInfo»
1313         «ENDIF»
1314     '''
1315
1316     private def dispatch nodeName(ContainerSchemaNode node) '''
1317         «IF node.isAddedBy»
1318             «strong(italic(node.QName.localName))»«node.addedByInfo»
1319         «ELSE»
1320             «strong(node.QName.localName)»«node.addedByInfo»
1321         «ENDIF»
1322     '''
1323
1324     private def dispatch nodeName(ListSchemaNode node) '''
1325         «IF node.isAddedBy»
1326             «strong(italic(node.QName.localName))» «IF node.keyDefinition !== null && !node.keyDefinition.empty»«node.listKeys»«ENDIF»«node.addedByInfo»
1327         «ELSE»
1328             «strong(node.QName.localName)» «IF node.keyDefinition !== null && !node.keyDefinition.empty»«node.listKeys»«ENDIF»
1329         «ENDIF»
1330     '''
1331
1332 }