f9652b65d5912539175fe0557bedf79cfc3d4bad
[mdsal.git] / binding / mdsal-binding-java-api-generator / src / main / java / org / opendaylight / mdsal / binding / java / api / generator / BaseTemplate.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.mdsal.binding.java.api.generator
9
10 import static org.opendaylight.mdsal.binding.model.util.BindingGeneratorUtil.encodeAngleBrackets
11
12 import com.google.common.base.CharMatcher
13 import com.google.common.base.Splitter
14 import com.google.common.collect.Iterables
15 import java.util.Arrays
16 import java.util.Collection
17 import java.util.HashMap
18 import java.util.List
19 import java.util.Map
20 import java.util.StringTokenizer
21 import java.util.regex.Pattern
22 import org.opendaylight.mdsal.binding.model.api.ConcreteType
23 import org.opendaylight.mdsal.binding.model.api.Constant
24 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
25 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
26 import org.opendaylight.mdsal.binding.model.api.GeneratedType
27 import org.opendaylight.mdsal.binding.model.api.MethodSignature
28 import org.opendaylight.mdsal.binding.model.api.Restrictions
29 import org.opendaylight.mdsal.binding.model.api.Type
30 import org.opendaylight.mdsal.binding.model.api.TypeMember
31 import org.opendaylight.mdsal.binding.model.api.YangSourceDefinition.Single
32 import org.opendaylight.mdsal.binding.model.api.YangSourceDefinition.Multiple
33 import org.opendaylight.mdsal.binding.model.util.Types
34 import org.opendaylight.yangtools.yang.common.QName
35 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode
36 import org.opendaylight.yangtools.yang.model.api.DocumentedNode
37 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
38 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition
39 import org.opendaylight.yangtools.yang.model.api.RpcDefinition
40 import org.opendaylight.yangtools.yang.model.api.SchemaNode
41
42 abstract class BaseTemplate {
43     protected val GeneratedType type;
44     protected val Map<String, String> importMap;
45
46     private static final char NEW_LINE = '\n'
47     private static final CharMatcher NL_MATCHER = CharMatcher.is(NEW_LINE)
48     private static final CharMatcher TAB_MATCHER = CharMatcher.is('\t')
49     private static final Pattern SPACES_PATTERN = Pattern.compile(" +")
50     private static final Splitter NL_SPLITTER = Splitter.on(NL_MATCHER)
51     private static final Pattern TAIL_COMMENT_PATTERN = Pattern.compile("*/", Pattern.LITERAL);
52
53     new(GeneratedType _type) {
54         if (_type === null) {
55             throw new IllegalArgumentException("Generated type reference cannot be NULL!")
56         }
57         this.type = _type;
58         this.importMap = new HashMap<String,String>()
59     }
60
61     def packageDefinition() '''package «type.packageName»;'''
62
63     final public def generate() {
64         val _body = body()
65         '''
66             «packageDefinition»
67             «imports»
68
69             «_body»
70         '''.toString
71     }
72
73     protected def imports() '''
74         «FOR entry : importMap.entrySet»
75             «IF !hasSamePackage(entry.value) && !isLocalInnerClass(entry.value)»
76                 import «entry.value».«entry.key»;
77             «ENDIF»
78         «ENDFOR»
79     '''
80
81     /**
82      * Checks if packages of generated type and imported type is the same
83      *
84      * @param importedTypePackageName the package name of imported type
85      * @return true if the packages are the same false otherwise
86      */
87     final private def boolean hasSamePackage(String importedTypePackageName) {
88         return type.packageName.equals(importedTypePackageName);
89     }
90
91     def isLocalInnerClass(String importedTypePackageName) {
92         return type.fullyQualifiedName.equals(importedTypePackageName);
93     }
94
95     protected abstract def CharSequence body();
96
97     // Helper patterns
98     final protected def fieldName(GeneratedProperty property) '''_«property.name»'''
99
100     final protected def propertyNameFromGetter(MethodSignature getter) {
101         var int prefix;
102         if (getter.name.startsWith("is")) {
103             prefix = 2
104         } else if (getter.name.startsWith("get")) {
105             prefix = 3
106         } else {
107             throw new IllegalArgumentException("Not a getter")
108         }
109         return getter.name.substring(prefix).toFirstLower;
110     }
111
112     final protected def isAccessor(MethodSignature maybeGetter) {
113         return maybeGetter.name.startsWith("is") || maybeGetter.name.startsWith("get");
114     }
115
116     /**
117      * Template method which generates the getter method for <code>field</code>
118      *
119      * @param field
120      * generated property with data about field which is generated as the getter method
121      * @return string with the getter method source code in JAVA format
122      */
123     protected def getterMethod(GeneratedProperty field) {
124         '''
125             public «field.returnType.importedName» «field.getterMethodName»() {
126                 «IF field.returnType.importedName.contains("[]")»
127                 return «field.fieldName» == null ? null : «field.fieldName».clone();
128                 «ELSE»
129                 return «field.fieldName»;
130                 «ENDIF»
131             }
132         '''
133     }
134
135     final protected def getterMethodName(GeneratedProperty field) {
136         val prefix = if(field.returnType.equals(Types.BOOLEAN)) "is" else "get"
137         return '''«prefix»«field.name.toFirstUpper»'''
138     }
139
140     /**
141      * Template method which generates the setter method for <code>field</code>
142      *
143      * @param field
144      * generated property with data about field which is generated as the setter method
145      * @return string with the setter method source code in JAVA format
146      */
147     final protected def setterMethod(GeneratedProperty field) '''
148         «val returnType = field.returnType.importedName»
149         public «type.name» set«field.name.toFirstUpper»(«returnType» value) {
150             this.«field.fieldName» = value;
151             return this;
152         }
153     '''
154
155     final protected def importedName(Type intype) {
156         GeneratorUtil.putTypeIntoImports(type, intype, importMap);
157         GeneratorUtil.getExplicitType(type, intype, importMap)
158     }
159
160     final protected def importedName(Class<?> cls) {
161         importedName(Types.typeForClass(cls))
162     }
163
164     /**
165      * Template method which generates method parameters with their types from <code>parameters</code>.
166      *
167      * @param parameters
168      * group of generated property instances which are transformed to the method parameters
169      * @return string with the list of the method parameters with their types in JAVA format
170      */
171     def final protected asArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
172         returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»'''
173
174     /**
175      * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
176      *
177      * @param parameters
178      * group of generated property instances which are transformed to the sequence of parameter names
179      * @return string with the list of the parameter names of the <code>parameters</code>
180      */
181     def final protected asArguments(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
182         fieldName»«ENDFOR»«ENDIF»'''
183
184     /**
185      * Template method which generates JAVA comments.
186      *
187      * @param comment string with the comment for whole JAVA class
188      * @return string with comment in JAVA format
189      */
190     def protected CharSequence asJavadoc(String comment) {
191         if(comment === null) return ''
192         var txt = comment
193
194         txt = comment.trim
195         txt = formatToParagraph(txt)
196
197         return '''
198             «wrapToDocumentation(txt)»
199         '''
200     }
201
202     def String wrapToDocumentation(String text) {
203         if (text.empty)
204             return ""
205
206         val StringBuilder sb = new StringBuilder("/**")
207         sb.append(NEW_LINE)
208
209         for (String t : NL_SPLITTER.split(text)) {
210             sb.append(" *")
211             if (!t.isEmpty()) {
212                 sb.append(' ');
213                 sb.append(t)
214             }
215             sb.append(NEW_LINE)
216         }
217         sb.append(" */")
218
219         return sb.toString
220     }
221
222     def protected String formatDataForJavaDoc(GeneratedType type) {
223         val sb = new StringBuilder()
224         val comment = type.comment
225         if (comment !== null) {
226             sb.append(comment.javadoc)
227         }
228
229         appendSnippet(sb, type)
230
231         return '''
232             «IF sb.length != 0»
233             «sb.toString»
234             «ENDIF»
235         '''.toString
236     }
237
238     private static val AMP_MATCHER = CharMatcher.is('&')
239
240     def static encodeJavadocSymbols(String description) {
241         if (description.nullOrEmpty) {
242             return description;
243         }
244
245         return TAIL_COMMENT_PATTERN.matcher(AMP_MATCHER.replaceFrom(description, "&amp;")).replaceAll("&#42;&#47;")
246     }
247
248     def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
249         val comment = type.comment
250         if (comment === null) {
251             return '''
252                 «additionalComment»
253             '''
254         }
255
256         val sb = new StringBuilder().append(comment.javadoc)
257         appendSnippet(sb, type)
258
259         sb.append(NEW_LINE)
260         .append(NEW_LINE)
261         .append(NEW_LINE)
262         .append(additionalComment)
263
264         return '''
265             «sb.toString»
266         '''
267     }
268
269     def private static void appendSnippet(StringBuilder sb, GeneratedType type) {
270         val optDef = type.yangSourceDefinition
271         if (optDef.present) {
272             val def = optDef.get
273             sb.append(NEW_LINE)
274
275                 if (def instanceof Single) {
276                 val node = def.node
277                 sb.append("<p>\n")
278                 .append("This class represents the following YANG schema fragment defined in module <b>")
279                 .append(def.module.name).append("</b>\n")
280                 .append("<pre>\n")
281                 .append(encodeAngleBrackets(encodeJavadocSymbols(YangTemplate.generateYangSnippet(node))))
282                 .append("</pre>")
283
284                 if (node instanceof SchemaNode) {
285                     sb.append("The schema path to identify an instance is\n")
286                     .append("<i>")
287                     .append(formatSchemaPath(def.module.name, node.path.pathFromRoot))
288                     .append("</i>\n")
289
290                     if (hasBuilderClass(node)) {
291                         val builderName = type.name + "Builder";
292
293                         sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
294                         .append("}.\n")
295                         .append("@see ").append(builderName).append('\n')
296                         if (node instanceof ListSchemaNode) {
297                             val keyDef = node.keyDefinition
298                             if (keyDef !== null && !keyDef.empty) {
299                                 sb.append("@see ").append(type.name).append("Key")
300                             }
301                             sb.append('\n');
302                         }
303                     }
304                 }
305                 } else if (def instanceof Multiple) {
306                 sb.append("<pre>\n")
307                 for (DocumentedNode schemaNode : def.nodes) {
308                     sb.append(encodeAngleBrackets(encodeJavadocSymbols(YangTemplate.generateYangSnippet(schemaNode))))
309                 }
310                 sb.append("</pre>\n")
311             }
312         }
313     }
314
315     def private static boolean hasBuilderClass(SchemaNode schemaNode) {
316         return schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode
317                 || schemaNode instanceof RpcDefinition || schemaNode instanceof NotificationDefinition;
318     }
319
320     def private static String formatSchemaPath(String moduleName, Iterable<QName> schemaPath) {
321         val sb = new StringBuilder().append(moduleName);
322
323         var currentElement = Iterables.getFirst(schemaPath, null);
324         for (QName pathElement : schemaPath) {
325             sb.append('/')
326             if (!currentElement.namespace.equals(pathElement.namespace)) {
327                 currentElement = pathElement
328                 sb.append(pathElement)
329             } else {
330                 sb.append(pathElement.getLocalName())
331             }
332         }
333         return sb.toString();
334     }
335
336     def protected String formatDataForJavaDoc(TypeMember type, String additionalComment) {
337         val StringBuilder typeDescriptionBuilder = new StringBuilder();
338         if (!type.comment.nullOrEmpty) {
339             typeDescriptionBuilder.append(formatToParagraph(type.comment))
340             typeDescriptionBuilder.append(NEW_LINE)
341             typeDescriptionBuilder.append(NEW_LINE)
342             typeDescriptionBuilder.append(NEW_LINE)
343         }
344         typeDescriptionBuilder.append(additionalComment)
345         var typeDescription = wrapToDocumentation(typeDescriptionBuilder.toString)
346         return '''
347             «typeDescription»
348         '''.toString
349     }
350
351     def asCode(String text) {
352         return "<code>" + text + "</code>"
353     }
354
355     def asLink(String text) {
356         val StringBuilder sb = new StringBuilder()
357         var tempText = text
358         var char lastChar = ' '
359         var boolean badEnding = false
360
361         if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
362             tempText = text.substring(0, text.length - 1)
363             lastChar = text.charAt(text.length - 1)
364             badEnding = true
365         }
366         sb.append("<a href = \"")
367         sb.append(tempText)
368         sb.append("\">")
369         sb.append(tempText)
370         sb.append("</a>")
371
372         if(badEnding)
373             sb.append(lastChar)
374
375         return sb.toString
376     }
377
378     protected def formatToParagraph(String text) {
379         if(text === null || text.isEmpty)
380             return text
381
382         var formattedText = text
383         val StringBuilder sb = new StringBuilder();
384         var StringBuilder lineBuilder = new StringBuilder();
385         var boolean isFirstElementOnNewLineEmptyChar = false;
386
387         formattedText = encodeJavadocSymbols(formattedText)
388         formattedText = NL_MATCHER.removeFrom(formattedText)
389         formattedText = TAB_MATCHER.removeFrom(formattedText)
390         formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
391
392         val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
393
394         while (tokenizer.hasMoreElements) {
395             val nextElement = tokenizer.nextElement.toString
396
397             if (lineBuilder.length != 0 && lineBuilder.length + nextElement.length > 80) {
398                 if (lineBuilder.charAt(lineBuilder.length - 1) == ' ') {
399                     lineBuilder.setLength(0)
400                     lineBuilder.append(lineBuilder.substring(0, lineBuilder.length - 1))
401                 }
402                 if (lineBuilder.charAt(0) == ' ') {
403                     lineBuilder.setLength(0)
404                     lineBuilder.append(lineBuilder.substring(1))
405                 }
406
407                 sb.append(lineBuilder);
408                 lineBuilder.setLength(0)
409                 sb.append(NEW_LINE)
410
411                 if(nextElement.toString == ' ') {
412                     isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
413                 }
414             }
415
416             if (isFirstElementOnNewLineEmptyChar) {
417                 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
418             }
419
420             else {
421                 lineBuilder.append(nextElement)
422             }
423         }
424         sb.append(lineBuilder)
425         sb.append(NEW_LINE)
426
427         return sb.toString
428     }
429
430     def protected generateToString(Collection<GeneratedProperty> properties) '''
431         «IF !properties.empty»
432             @Override
433             public «String.importedName» toString() {
434                 «StringBuilder.importedName» builder = new «StringBuilder.importedName»(«type.importedName».class.getSimpleName()).append(" [");
435                 boolean first = true;
436
437                 «FOR property : properties»
438                     if («property.fieldName» != null) {
439                         if (first) {
440                             first = false;
441                         } else {
442                             builder.append(", ");
443                         }
444                         builder.append("«property.fieldName»=");
445                         «IF property.returnType.name.contains("[")»
446                             builder.append(«Arrays.importedName».toString(«property.fieldName»));
447                         «ELSE»
448                             builder.append(«property.fieldName»);
449                         «ENDIF»
450                     }
451                 «ENDFOR»
452                 return builder.append(']').toString();
453             }
454         «ENDIF»
455     '''
456
457     def getRestrictions(Type type) {
458         var Restrictions restrictions = null
459         if (type instanceof ConcreteType) {
460             restrictions = type.restrictions
461         } else if (type instanceof GeneratedTransferObject) {
462             restrictions = type.restrictions
463         }
464         return restrictions
465     }
466
467     /**
468      * Template method which generates method parameters with their types from <code>parameters</code>.
469      *
470      * @param parameters
471      * list of parameter instances which are transformed to the method parameters
472      * @return string with the list of the method parameters with their types in JAVA format
473      */
474     def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
475         IF !parameters.empty»«
476             FOR parameter : parameters SEPARATOR ", "»«
477                 parameter.type.importedName» «parameter.name»«
478             ENDFOR»«
479         ENDIF
480     »'''
481
482     def protected GeneratedProperty findProperty(GeneratedTransferObject gto, String name) {
483         val props = gto.properties
484         for (prop : props) {
485             if (prop.name.equals(name)) {
486                 return prop
487             }
488         }
489         val GeneratedTransferObject parent = gto.superType
490         if (parent !== null) {
491             return findProperty(parent, name)
492         }
493         return null
494     }
495
496     def protected emitConstant(Constant c) '''
497         «IF c.value instanceof QName»
498             «val qname = c.value as QName»
499             «val rev = qname.revision»
500             public static final «c.type.importedName» «c.name» = «QName.name».create("«qname.namespace.toString»",
501                 «IF rev.isPresent»"«rev.get»", «ENDIF»"«qname.localName»").intern();
502         «ELSE»
503             public static final «c.type.importedName» «c.name» = «c.value»;
504         «ENDIF»
505     '''
506 }