Merge "Bug 735 - Part 1: Update ietf-restconf and ietf-yangtypes to newer versions"
[yangtools.git] / code-generator / binding-java-api-generator / src / main / java / org / opendaylight / yangtools / sal / java / api / generator / BuilderTemplate.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.sal.java.api.generator
9
10 import java.util.Arrays;
11 import java.util.LinkedHashSet
12 import java.util.List
13 import java.util.Map
14 import java.util.Set
15 import org.opendaylight.yangtools.binding.generator.util.ReferencedTypeImpl
16 import org.opendaylight.yangtools.binding.generator.util.Types
17 import org.opendaylight.yangtools.binding.generator.util.generated.type.builder.GeneratedTOBuilderImpl
18 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedProperty
19 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedTransferObject
20 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType
21 import org.opendaylight.yangtools.sal.binding.model.api.MethodSignature
22 import org.opendaylight.yangtools.sal.binding.model.api.Type
23 import org.opendaylight.yangtools.yang.binding.Augmentable
24 import static org.opendaylight.yangtools.binding.generator.util.Types.*
25 import java.util.HashMap
26 import java.util.Collections
27 import org.opendaylight.yangtools.yang.binding.DataObject
28 import java.util.ArrayList
29 import java.util.HashSet
30 import java.util.Collection
31 import org.opendaylight.yangtools.yang.binding.Identifiable
32
33 /**
34  * Template for generating JAVA builder classes. 
35  */
36
37 class BuilderTemplate extends BaseTemplate {
38
39     /**
40      * Constant with the name of the concrete method.
41      */
42     val static GET_AUGMENTATION_METHOD_NAME = "getAugmentation"
43
44     /**
45      * Constant with the suffix for builder classes.
46      */
47     val static BUILDER = 'Builder'
48
49     /**
50      * Constant with suffix for the classes which are generated from the builder classes.
51      */
52     val static IMPL = 'Impl'
53
54     /**
55      * Generated property is set if among methods is found one with the name GET_AUGMENTATION_METHOD_NAME
56      */
57     var GeneratedProperty augmentField
58
59     /**
60      * Set of class attributes (fields) which are derived from the getter methods names
61      */
62     val Set<GeneratedProperty> properties
63
64     /**
65      * Constructs new instance of this class.
66      * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
67      */
68     new(GeneratedType genType) {
69         super(genType)
70         this.properties = propertiesFromMethods(createMethods)
71     }
72
73     /**
74      * Returns set of method signature instances which contains all the methods of the <code>genType</code>
75      * and all the methods of the implemented interfaces.
76      * 
77      * @returns set of method signature instances
78      */
79     def private Set<MethodSignature> createMethods() {
80         val Set<MethodSignature> methods = new LinkedHashSet
81         methods.addAll(type.methodDefinitions)
82         collectImplementedMethods(methods, type.implements)
83         return methods
84     }
85
86     /**
87      * Adds to the <code>methods</code> set all the methods of the <code>implementedIfcs</code> 
88      * and recursivelly their implemented interfaces.
89      * 
90      * @param methods set of method signatures
91      * @param implementedIfcs list of implemented interfaces
92      */
93     def private void collectImplementedMethods(Set<MethodSignature> methods, List<Type> implementedIfcs) {
94         if (implementedIfcs == null || implementedIfcs.empty) {
95             return
96         }
97         for (implementedIfc : implementedIfcs) {
98             if ((implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))) {
99                 val ifc = implementedIfc as GeneratedType
100                 methods.addAll(ifc.methodDefinitions)
101                 collectImplementedMethods(methods, ifc.implements)
102             } else if (implementedIfc.fullyQualifiedName == Augmentable.name) {
103                 for (m : Augmentable.methods) {
104                     if (m.name == GET_AUGMENTATION_METHOD_NAME) {
105                         val fullyQualifiedName = m.returnType.name
106                         val pkg = fullyQualifiedName.package
107                         val name = fullyQualifiedName.name
108                         val tmpGenTO = new GeneratedTOBuilderImpl(pkg, name)
109                         val refType = new ReferencedTypeImpl(pkg, name)
110                         val generic = new ReferencedTypeImpl(type.packageName, type.name)
111                         val parametrizedReturnType = Types.parameterizedTypeFor(refType, generic)
112                         tmpGenTO.addMethod(m.name).setReturnType(parametrizedReturnType)
113                         augmentField = tmpGenTO.toInstance.methodDefinitions.first.propertyFromGetter
114                     }
115                 }
116             }
117         }
118     }
119
120     /**
121      * Returns the first element of the list <code>elements</code>.
122      * 
123      * @param list of elements
124      */
125     def private <E> first(List<E> elements) {
126         elements.get(0)
127     }
128
129     /**
130      * Returns the name of the package from <code>fullyQualifiedName</code>.
131      * 
132      * @param fullyQualifiedName string with fully qualified type name (package + type)
133      * @return string with the package name
134      */
135     def private String getPackage(String fullyQualifiedName) {
136         val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
137         return if (lastDotIndex == -1) "" else fullyQualifiedName.substring(0, lastDotIndex)
138     }
139
140         /**
141          * Returns the name of tye type from <code>fullyQualifiedName</code>
142          * 
143          * @param fullyQualifiedName string with fully qualified type name (package + type)
144          * @return string with the name of the type
145          */
146     def private String getName(String fullyQualifiedName) {
147         val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
148         return if (lastDotIndex == -1) fullyQualifiedName else fullyQualifiedName.substring(lastDotIndex + 1)
149     }
150
151     /**
152      * Creates set of generated property instances from getter <code>methods</code>.
153      * 
154      * @param set of method signature instances which should be transformed to list of properties 
155      * @return set of generated property instances which represents the getter <code>methods</code>
156      */
157     def private propertiesFromMethods(Set<MethodSignature> methods) {
158         if (methods == null || methods.isEmpty()) {
159             return Collections.emptySet
160         }
161         val Set<GeneratedProperty> result = new LinkedHashSet
162         for (m : methods) {
163             val createdField = m.propertyFromGetter
164             if (createdField != null) {
165                 result.add(createdField)
166             }
167         }
168         return result
169     }
170
171     /**
172      * Creates generated property instance from the getter <code>method</code> name and return type.
173      * 
174      * @param method method signature from which is the method name and return type obtained
175      * @return generated property instance for the getter <code>method</code>
176      * @throws IllegalArgumentException<ul>
177      *  <li>if the <code>method</code> equals <code>null</code></li>
178      *  <li>if the name of the <code>method</code> equals <code>null</code></li>
179      *  <li>if the name of the <code>method</code> is empty</li>
180      *  <li>if the return type of the <code>method</code> equals <code>null</code></li>
181      * </ul>
182      */
183     def private GeneratedProperty propertyFromGetter(MethodSignature method) {
184         if (method == null || method.name == null || method.name.empty || method.returnType == null) {
185             throw new IllegalArgumentException("Method, method name, method return type reference cannot be NULL or empty!")
186         }
187         var prefix = "get";
188         if(BOOLEAN.equals(method.returnType)) {
189             prefix = "is";
190         } 
191         if (method.name.startsWith(prefix)) {
192             val fieldName = method.getName().substring(prefix.length()).toFirstLower
193             val tmpGenTO = new GeneratedTOBuilderImpl("foo", "foo")
194             tmpGenTO.addProperty(fieldName).setReturnType(method.returnType)
195             return tmpGenTO.toInstance.properties.first
196         }
197     }
198
199     /**
200      * Template method which generates JAVA class body for builder class and for IMPL class. 
201      * 
202      * @return string with JAVA source code
203      */
204     override body() '''
205
206         public class «type.name»«BUILDER» {
207
208             «generateFields(false)»
209
210             «generateAugmentField(true)»
211
212             «generateConstructorsFromIfcs(type)»
213
214             «generateMethodFieldsFrom(type)»
215
216             «generateGetters(false)»
217
218             «generateSetters»
219
220             public «type.name» build() {
221                 return new «type.name»«IMPL»(this);
222             }
223
224             private static final class «type.name»«IMPL» implements «type.name» {
225
226                 «implementedInterfaceGetter»
227
228                 «generateFields(true)»
229
230                 «generateAugmentField(false)»
231
232                 «generateConstructor»
233
234                 «generateGetters(true)»
235
236                 «generateHashCode()»
237
238                 «generateEquals()»
239                 
240                 «generateToString(properties)»
241             }
242
243         }
244     '''
245
246     /**
247      * Generate default constructor and constructor for every implemented interface from uses statements.
248      */
249     def private generateConstructorsFromIfcs(Type type) '''
250         public «type.name»«BUILDER»() {
251         } 
252         «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
253             «val ifc = type as GeneratedType»
254             «FOR impl : ifc.implements»
255                 «generateConstructorFromIfc(impl)»
256             «ENDFOR»
257         «ENDIF»
258     '''
259
260     /**
261      * Generate constructor with argument of given type.
262      */
263     def private Object generateConstructorFromIfc(Type impl) '''
264         «IF (impl instanceof GeneratedType)»
265             «val implType = impl as GeneratedType»
266
267             «IF !(implType.methodDefinitions.empty)»
268                 public «type.name»«BUILDER»(«implType.fullyQualifiedName» arg) {
269                     «printConstructorPropertySetter(implType)»
270                 }
271             «ENDIF»
272             «FOR implTypeImplement : implType.implements»
273                 «generateConstructorFromIfc(implTypeImplement)»
274             «ENDFOR»
275         «ENDIF»
276     '''
277
278     def private Object printConstructorPropertySetter(Type implementedIfc) '''
279         «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
280             «val ifc = implementedIfc as GeneratedType»
281             «FOR getter : ifc.methodDefinitions»
282                 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
283             «ENDFOR»
284             «FOR impl : ifc.implements»
285                 «printConstructorPropertySetter(impl)»
286             «ENDFOR»
287         «ENDIF»
288     '''
289
290     /**
291      * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
292      */
293     def private generateMethodFieldsFrom(Type type) '''
294         «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
295             «val ifc = type as GeneratedType»
296             «IF ifc.hasImplementsFromUses»
297                 «val List<Type> done = ifc.getBaseIfcs»
298                 «generateMethodFieldsFromComment(ifc)»
299                 public void fieldsFrom(«DataObject.importedName» arg) {
300                     boolean isValidArg = false;
301                     «FOR impl : ifc.getAllIfcs»
302                         «generateIfCheck(impl, done)»
303                     «ENDFOR»
304                     if (!isValidArg) {
305                         throw new IllegalArgumentException(
306                           "expected one of: «ifc.getAllIfcs.toListOfNames» \n" +
307                           "but was: " + arg
308                         );
309                     }
310                 }
311             «ENDIF»
312         «ENDIF»
313     '''
314
315     def private generateMethodFieldsFromComment(GeneratedType type) '''
316         /**
317          Set fields from given grouping argument. Valid argument is instance of one of following types:
318          * <ul>
319          «FOR impl : type.getAllIfcs»
320          * <li>«impl.fullyQualifiedName»</li>
321          «ENDFOR»
322          * </ul>
323          *
324          * @param arg grouping object
325          * @throws IllegalArgumentException if given argument is none of valid types
326         */
327     '''
328
329     /**
330      * Method is used to find out if given type implements any interface from uses.
331      */
332     def boolean hasImplementsFromUses(GeneratedType type) {
333         var i = 0
334         for (impl : type.getAllIfcs) {
335             if ((impl instanceof GeneratedType) &&  !((impl as GeneratedType).methodDefinitions.empty)) {
336                 i = i + 1
337             }
338         }
339         return i > 0
340     }
341
342     def private generateIfCheck(Type impl, List<Type> done) '''
343         «IF (impl instanceof GeneratedType) &&  !((impl as GeneratedType).methodDefinitions.empty)»
344             «val implType = impl as GeneratedType»
345             if (arg instanceof «implType.fullyQualifiedName») {
346                 «printPropertySetter(implType)»
347                 isValidArg = true;
348             }
349         «ENDIF»
350     '''
351
352     def private printPropertySetter(Type implementedIfc) '''
353         «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
354         «val ifc = implementedIfc as GeneratedType»
355         «FOR getter : ifc.methodDefinitions»
356             this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
357         «ENDFOR»
358         «ENDIF»
359     '''
360
361     private def List<Type> getBaseIfcs(GeneratedType type) {
362         val List<Type> baseIfcs = new ArrayList();
363         for (ifc : type.implements) {
364             if (ifc instanceof GeneratedType && !(ifc as GeneratedType).methodDefinitions.empty) {
365                 baseIfcs.add(ifc)
366             }
367         }
368         return baseIfcs 
369     }
370
371     private def Set<Type> getAllIfcs(Type type) {
372         val Set<Type> baseIfcs = new HashSet()
373         if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
374             val ifc = type as GeneratedType
375             for (impl : ifc.implements) {
376                 if (impl instanceof GeneratedType && !(impl as GeneratedType).methodDefinitions.empty) {
377                     baseIfcs.add(impl)
378                 }
379                 baseIfcs.addAll(impl.getAllIfcs)
380             }
381         }
382         return baseIfcs 
383     }
384
385     private def List<String> toListOfNames(Collection<Type> types) {
386         val List<String> names = new ArrayList
387         for (type : types) {
388             names.add(type.fullyQualifiedName)
389         }
390         return names
391     }
392
393         /**
394          * Template method which generates class attributes.
395          * 
396          * @param boolean value which specify whether field is|isn't final
397          * @return string with class attributes and their types
398          */
399     def private generateFields(boolean _final) '''
400         «IF !properties.empty»
401             «FOR f : properties»
402                 private«IF _final» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
403             «ENDFOR»
404         «ENDIF»
405     '''
406
407     def private generateAugmentField(boolean init) '''
408         «IF augmentField != null»
409             private final «Map.importedName»<Class<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> «augmentField.name»«IF init» = new «HashMap.importedName»<>()«ENDIF»;
410         «ENDIF»
411     '''
412
413         /**
414          * Template method which generates setter methods
415          * 
416          * @return string with the setter methods 
417          */
418     def private generateSetters() '''
419         «FOR field : properties SEPARATOR '\n'»
420             public «type.name»«BUILDER» set«field.name.toFirstUpper»(«field.returnType.importedName» value) {
421                 «generateRestrictions(field, "value")»
422
423                 this.«field.fieldName» = value;
424                 return this;
425             }
426         «ENDFOR»
427         «IF augmentField != null»
428
429             public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(Class<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentation) {
430                 this.«augmentField.name».put(augmentationType, augmentation);
431                 return this;
432             }
433         «ENDIF»
434     '''
435
436     /**
437      * Template method which generate constructor for IMPL class.
438      * 
439      * @return string with IMPL class constructor
440      */
441     def private generateConstructor() '''
442         private «type.name»«IMPL»(«type.name»«BUILDER» builder) {
443             «val allProps = new ArrayList(properties)»
444             «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
445             «val keyType = type.getKey»
446             «IF isList && keyType != null»
447                 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
448                 «Collections.sort(keyProps,
449                     [ p1, p2 |
450                         return p1.name.compareTo(p2.name)
451                     ])
452                 »
453                 «FOR field : keyProps»
454                     «removeProperty(allProps, field.name)»
455                 «ENDFOR»
456                 «removeProperty(allProps, "key")»
457                 if (builder.getKey() == null) {
458                     this._key = new «keyType.importedName»(
459                         «FOR keyProp : keyProps SEPARATOR ", "»
460                             builder.«keyProp.getterMethodName»()
461                         «ENDFOR»
462                     );
463                     «FOR field : keyProps»
464                         this.«field.fieldName» = builder.«field.getterMethodName»();
465                     «ENDFOR»
466                 } else {
467                     this._key = builder.getKey();
468                     «FOR field : keyProps»
469                            this.«field.fieldName» = _key.«field.getterMethodName»();
470                     «ENDFOR»
471                 }
472             «ENDIF»
473             «FOR field : allProps»
474                 this.«field.fieldName» = builder.«field.getterMethodName»();
475             «ENDFOR»
476             «IF augmentField != null»
477                switch (builder.«augmentField.name».size()) {
478                 case 0:
479                     this.«augmentField.name» = «Collections.importedName».emptyMap();
480                     break;
481                 case 1:
482                     final «Map.importedName».Entry<Class<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e = builder.«augmentField.name».entrySet().iterator().next();
483                     this.«augmentField.name» = «Collections.importedName».<Class<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»>singletonMap(e.getKey(), e.getValue());
484                     break;
485                 default :
486                     this.«augmentField.name» = new «HashMap.importedName»<>(builder.«augmentField.name»);
487                 }
488             «ENDIF»
489         }
490     '''
491
492     private def boolean implementsIfc(GeneratedType type, Type impl) {
493         for (Type ifc : type.implements) {
494             if (ifc.equals(impl)) {
495                 return true;
496             }
497         }
498         return false;
499     }
500
501     private def Type getKey(GeneratedType type) {
502         for (m : type.methodDefinitions) {
503             if ("getKey".equals(m.name)) {
504                 return m.returnType;
505             }
506         }
507         return null;
508     }
509
510     private def void removeProperty(Collection<GeneratedProperty> props, String name) {
511         var GeneratedProperty toRemove = null
512         for (p : props) {
513             if (p.name.equals(name)) {
514                 toRemove = p;
515             }
516         }
517         if (toRemove != null) {
518             props.remove(toRemove);
519         }
520     }
521
522     /**
523      * Template method which generate getter methods for IMPL class.
524      * 
525      * @return string with getter methods
526      */
527     def private generateGetters(boolean addOverride) '''
528         «IF !properties.empty»
529             «FOR field : properties SEPARATOR '\n'»
530                 «IF addOverride»@Override«ENDIF»
531                 «field.getterMethod»
532             «ENDFOR»
533         «ENDIF»
534         «IF augmentField != null»
535
536             @SuppressWarnings("unchecked")
537             «IF addOverride»@Override«ENDIF»
538             public <E extends «augmentField.returnType.importedName»> E get«augmentField.name.toFirstUpper»(Class<E> augmentationType) {
539                 if (augmentationType == null) {
540                     throw new IllegalArgumentException("Augmentation Type reference cannot be NULL!");
541                 }
542                 return (E) «augmentField.name».get(augmentationType);
543             }
544         «ENDIF»
545     '''
546
547     /**
548      * Template method which generates the method <code>hashCode()</code>.
549      * 
550      * @return string with the <code>hashCode()</code> method definition in JAVA format
551      */
552     def protected generateHashCode() '''
553         «IF !properties.empty || augmentField != null»
554             @Override
555             public int hashCode() {
556                 final int prime = 31;
557                 int result = 1;
558                 «FOR property : properties»
559                     «IF property.returnType.name.contains("[")»
560                     result = prime * result + ((«property.fieldName» == null) ? 0 : «Arrays.importedName».hashCode(«property.fieldName»));
561                     «ELSE»
562                     result = prime * result + ((«property.fieldName» == null) ? 0 : «property.fieldName».hashCode());
563                     «ENDIF»
564                 «ENDFOR»
565                 «IF augmentField != null»
566                     result = prime * result + ((«augmentField.name» == null) ? 0 : «augmentField.name».hashCode());
567                 «ENDIF»
568                 return result;
569             }
570         «ENDIF»
571     '''
572
573     /**
574      * Template method which generates the method <code>equals()</code>.
575      * 
576      * @return string with the <code>equals()</code> method definition in JAVA format     
577      */
578     def protected generateEquals() '''
579         «IF !properties.empty || augmentField != null»
580             @Override
581             public boolean equals(java.lang.Object obj) {
582                 if (this == obj) {
583                     return true;
584                 }
585                 if (obj == null) {
586                     return false;
587                 }
588                 if (getClass() != obj.getClass()) {
589                     return false;
590                 }
591                 «type.name»«IMPL» other = («type.name»«IMPL») obj;
592                 «FOR property : properties»
593                     «val fieldName = property.fieldName»
594                     if («fieldName» == null) {
595                         if (other.«fieldName» != null) {
596                             return false;
597                         }
598                     «IF property.returnType.name.contains("[")»
599                     } else if(!«Arrays.importedName».equals(«fieldName», other.«fieldName»)) {
600                     «ELSE»
601                     } else if(!«fieldName».equals(other.«fieldName»)) {
602                     «ENDIF»
603                         return false;
604                     }
605                 «ENDFOR»
606                 «IF augmentField != null»
607                     «val fieldName = augmentField.name»
608                     if («fieldName» == null) {
609                         if (other.«fieldName» != null) {
610                             return false;
611                         }
612                     } else if(!«fieldName».equals(other.«fieldName»)) {
613                         return false;
614                     }
615                 «ENDIF»
616                 return true;
617             }
618         «ENDIF»
619     '''
620
621     def override generateToString(Collection<GeneratedProperty> properties) '''
622         «IF !(properties === null)»
623             @Override
624             public String toString() {
625                 StringBuilder builder = new StringBuilder("«type.name» [");
626                 boolean first = true;
627
628                 «FOR property : properties»
629                     if («property.fieldName» != null) {
630                         if (first) {
631                             first = false;
632                         } else {
633                             builder.append(", ");
634                         }
635                         builder.append("«property.fieldName»=");
636                         «IF property.returnType.name.contains("[")»
637                             builder.append(«Arrays.importedName».toString(«property.fieldName»));
638                         «ELSE»
639                             builder.append(«property.fieldName»);
640                         «ENDIF»
641                      }
642                 «ENDFOR»
643                 «IF augmentField != null»
644                     if (first) {
645                         first = false;
646                     } else {
647                         builder.append(", ");
648                     }
649                     builder.append("«augmentField.name»=");
650                     builder.append(«augmentField.name».values());
651                 «ENDIF»
652                 return builder.append(']').toString();
653             }
654         «ENDIF»
655     '''
656
657     override protected getFullyQualifiedName() {
658         '''«type.fullyQualifiedName»Builder'''.toString
659     }
660
661     def implementedInterfaceGetter() '''
662     public «Class.importedName»<«type.importedName»> getImplementedInterface() {
663         return «type.importedName».class;
664     }
665     '''
666
667 }
668