61dbcf89a4809d91825cf09caf31c7b433a3c0cb
[mdsal.git] / binding / mdsal-binding-java-api-generator / src / main / java / org / opendaylight / mdsal / binding / java / api / generator / InterfaceTemplate.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 extension org.opendaylight.mdsal.binding.spec.naming.BindingMapping.getGetterMethodForNonnull
11 import static extension org.opendaylight.mdsal.binding.spec.naming.BindingMapping.isGetterMethodName
12 import static extension org.opendaylight.mdsal.binding.spec.naming.BindingMapping.isNonnullMethodName
13 import static org.opendaylight.mdsal.binding.model.util.Types.STRING;
14 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTATION_FIELD
15 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.BINDING_EQUALS_NAME
16 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.BINDING_HASHCODE_NAME
17 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.BINDING_TO_STRING_NAME
18 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME
19
20 import com.google.common.base.MoreObjects
21 import java.util.List
22 import java.util.Map.Entry
23 import java.util.Set
24 import org.gaul.modernizer_maven_annotations.SuppressModernizer
25 import org.opendaylight.mdsal.binding.model.api.AnnotationType
26 import org.opendaylight.mdsal.binding.model.api.Constant
27 import org.opendaylight.mdsal.binding.model.api.Enumeration
28 import org.opendaylight.mdsal.binding.model.api.GeneratedType
29 import org.opendaylight.mdsal.binding.model.api.MethodSignature
30 import org.opendaylight.mdsal.binding.model.api.Type
31 import org.opendaylight.mdsal.binding.model.util.Types
32 import org.opendaylight.mdsal.binding.model.util.TypeConstants
33
34 /**
35  * Template for generating JAVA interfaces.
36  */
37  @SuppressModernizer
38 class InterfaceTemplate extends BaseTemplate {
39     /**
40      * List of constant instances which are generated as JAVA public static final attributes.
41      */
42     val List<Constant> consts
43
44     /**
45      * List of method signatures which are generated as method declarations.
46      */
47     val List<MethodSignature> methods
48
49     /**
50      * List of enumeration which are generated as JAVA enum type.
51      */
52     val List<Enumeration> enums
53
54     /**
55      * List of generated types which are enclosed inside <code>genType</code>
56      */
57     val List<GeneratedType> enclosedGeneratedTypes
58
59     var Entry<Type, Set<BuilderGeneratedProperty>> typeAnalysis
60
61     /**
62      * Creates the instance of this class which is used for generating the interface file source
63      * code from <code>genType</code>.
64      *
65      * @throws NullPointerException if <code>genType</code> is <code>null</code>
66      */
67     new(GeneratedType genType) {
68         super(genType)
69         consts = genType.constantDefinitions
70         methods = genType.methodDefinitions
71         enums = genType.enumerations
72         enclosedGeneratedTypes = genType.enclosedTypes
73     }
74
75     /**
76      * Template method which generate the whole body of the interface.
77      *
78      * @return string with code for interface body in JAVA format
79      */
80     override body() '''
81         «wrapToDocumentation(formatDataForJavaDoc(type))»
82         «type.annotations.generateAnnotations»
83         public interface «type.name»
84             «superInterfaces»
85         {
86
87             «generateInnerClasses»
88
89             «generateEnums»
90
91             «generateConstants»
92
93             «generateMethods»
94
95         }
96
97     '''
98
99     def private generateAnnotations(List<AnnotationType> annotations) '''
100         «IF annotations !== null && !annotations.empty»
101             «FOR annotation : annotations»
102                 «annotation.generateAnnotation»
103             «ENDFOR»
104         «ENDIF»
105     '''
106
107     /**
108      * Template method which generates the interface name declaration.
109      *
110      * @return string with the code for the interface declaration in JAVA format
111      */
112     def private superInterfaces()
113     '''
114     «IF (!type.implements.empty)»
115          extends
116          «FOR type : type.implements SEPARATOR ","»
117              «type.importedName»
118          «ENDFOR»
119      « ENDIF»
120      '''
121
122     /**
123      * Template method which generates inner classes inside this interface.
124      *
125      * @return string with the source code for inner classes in JAVA format
126      */
127     def private generateInnerClasses() '''
128         «IF !enclosedGeneratedTypes.empty»
129             «FOR innerClass : enclosedGeneratedTypes SEPARATOR "\n"»
130                 «generateInnerClass(innerClass)»
131             «ENDFOR»
132         «ENDIF»
133     '''
134
135     /**
136      * Template method which generates JAVA enum type.
137      *
138      * @return string with inner enum source code in JAVA format
139      */
140     def private generateEnums() '''
141         «IF !enums.empty»
142             «FOR e : enums SEPARATOR "\n"»
143                 «val enumTemplate = new EnumTemplate(javaType.getEnclosedType(e.identifier), e)»
144                 «enumTemplate.generateAsInnerClass»
145             «ENDFOR»
146         «ENDIF»
147     '''
148
149     /**
150      * Template method wich generates JAVA constants.
151      *
152      * @return string with constants in JAVA format
153      */
154     def private generateConstants() '''
155         «IF !consts.empty»
156             «FOR c : consts»
157                 «IF !c.name.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
158                     «emitConstant(c)»
159                 «ENDIF»
160             «ENDFOR»
161         «ENDIF»
162     '''
163
164     /**
165      * Template method which generates the declaration of the methods.
166      *
167      * @return string with the declaration of methods source code in JAVA format
168      */
169     def private generateMethods() '''
170         «IF !methods.empty»
171             «FOR m : methods SEPARATOR "\n"»
172                 «IF m.isDefault»
173                     «generateDefaultMethod(m)»
174                 «ELSEIF m.isStatic»
175                     «generateStaticMethod(m)»
176                 «ELSEIF m.parameters.empty && m.name.isGetterMethodName»
177                     «generateAccessorMethod(m)»
178                 «ELSE»
179                     «generateMethod(m)»
180                 «ENDIF»
181             «ENDFOR»
182         «ENDIF»
183     '''
184
185     def private generateDefaultMethod(MethodSignature method) {
186         if (method.name.isNonnullMethodName) {
187             generateNonnullMethod(method)
188         } else {
189             switch method.name {
190                 case DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME : generateDefaultImplementedInterface
191             }
192         }
193     }
194
195     def private generateStaticMethod(MethodSignature method) {
196         switch method.name {
197             case BINDING_EQUALS_NAME : generateBindingEquals
198             case BINDING_HASHCODE_NAME : generateBindingHashCode
199             case BINDING_TO_STRING_NAME : generateBindingToString
200         }
201     }
202
203     def private generateMethod(MethodSignature method) '''
204         «method.comment.asJavadoc»
205         «method.annotations.generateAnnotations»
206         «method.returnType.importedName» «method.name»(«method.parameters.generateParameters»);
207     '''
208
209     def private generateAccessorMethod(MethodSignature method) '''
210         «val ret = method.returnType»
211         «formatDataForJavaDoc(method, "@return " + asCode(ret.fullyQualifiedName) + " " + asCode(propertyNameFromGetter(method)) + ", or " + asCode("null") + " if not present")»
212         «method.annotations.generateAnnotations»
213         «nullableType(ret)» «method.name»();
214     '''
215
216     def private generateDefaultImplementedInterface() '''
217         @«OVERRIDE.importedName»
218         default «CLASS.importedName»<«type.fullyQualifiedName»> «DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME»() {
219             return «type.fullyQualifiedName».class;
220         }
221     '''
222
223     def private generateBindingHashCode() '''
224         «val augmentable = analyzeType»
225         /**
226          * Default implementation of {@link «Object.importedName»#hashCode()} contract for this interface.
227          * Implementations of this interface are encouraged to defer to this method to get consistent hashing
228          * results across all implementations.
229          *
230          «IF augmentable»
231          * @param <T$$> implementation type, which has to also implement «AUGMENTATION_HOLDER.importedName» interface
232          *              contract.
233          «ENDIF»
234          * @param obj Object for which to generate hashCode() result.
235          * @return Hash code value of data modeled by this interface.
236          * @throws «NPE.importedName» if {@code obj} is null
237          */
238         «IF augmentable»
239         static <T$$ extends «type.fullyQualifiedName» & «AUGMENTATION_HOLDER.importedName»<?>> int «BINDING_HASHCODE_NAME»(final @«NONNULL.importedName» T$$ obj) {
240         «ELSE»
241         static int «BINDING_HASHCODE_NAME»(final «type.fullyQualifiedName» obj) {
242         «ENDIF»
243             final int prime = 31;
244             int result = 1;
245             «FOR property : typeAnalysis.value»
246                 result = prime * result + «property.importedUtilClass».hashCode(obj.«property.getterMethodName»());
247             «ENDFOR»
248             «IF augmentable»
249                 result = prime * result + «CODEHELPERS.importedName».hashAugmentations(obj);
250             «ENDIF»
251             return result;
252         }
253     '''
254
255     def private generateBindingEquals() '''
256         «val augmentable = analyzeType»
257         «IF augmentable || !typeAnalysis.value.isEmpty»
258             /**
259              * Default implementation of {@link «Object.importedName»#equals(«Object.importedName»)} contract for this interface.
260              * Implementations of this interface are encouraged to defer to this method to get consistent equality
261              * results across all implementations.
262              *
263              «IF augmentable»
264              * @param <T$$> implementation type, which has to also implement «AUGMENTATION_HOLDER.importedName» interface
265              *              contract.
266              «ENDIF»
267              * @param thisObj Object acting as the receiver of equals invocation
268              * @param obj Object acting as argument to equals invocation
269              * @return True if thisObj and obj are considered equal
270              * @throws «NPE.importedName» if {@code thisObj} is null
271              */
272             «IF augmentable»
273             static <T$$ extends «type.fullyQualifiedName» & «AUGMENTATION_HOLDER.importedName»<«type.fullyQualifiedName»>> boolean «BINDING_EQUALS_NAME»(final @«NONNULL.importedName» T$$ thisObj, final «Types.objectType().importedName» obj) {
274             «ELSE»
275             static boolean «BINDING_EQUALS_NAME»(final «type.fullyQualifiedName» thisObj, final «Types.objectType().importedName» obj) {
276             «ENDIF»
277                 if (thisObj == obj) {
278                     return true;
279                 }
280                 final «type.fullyQualifiedName» other = «CODEHELPERS.importedName».checkCast(«type.fullyQualifiedName».class, obj);
281                 if (other == null) {
282                     return false;
283                 }
284                 «FOR property : ByTypeMemberComparator.sort(typeAnalysis.value)»
285                     if (!«property.importedUtilClass».equals(thisObj.«property.getterName»(), other.«property.getterName»())) {
286                         return false;
287                     }
288                 «ENDFOR»
289                 return «IF augmentable»«CODEHELPERS.importedName».equalsAugmentations(thisObj, other)«ELSE»true«ENDIF»;
290             }
291         «ENDIF»
292     '''
293
294     def generateBindingToString() '''
295         «val augmentable = analyzeType»
296         /**
297          * Default implementation of {@link «Object.importedName»#toString()} contract for this interface.
298          * Implementations of this interface are encouraged to defer to this method to get consistent string
299          * representations across all implementations.
300          *
301          «IF augmentable»
302          * @param <T$$> implementation type, which has to also implement «AUGMENTATION_HOLDER.importedName» interface
303          *              contract.
304          «ENDIF»
305          * @param obj Object for which to generate toString() result.
306          * @return {@link «STRING.importedName»} value of data modeled by this interface.
307          * @throws «NPE.importedName» if {@code obj} is null
308          */
309         «IF augmentable»
310         static <T$$ extends «type.fullyQualifiedName» & «AUGMENTATION_HOLDER.importedName»<«type.fullyQualifiedName»>> «STRING.importedName» «BINDING_TO_STRING_NAME»(final @«NONNULL.importedName» T$$ obj) {
311         «ELSE»
312         static «STRING.importedName» «BINDING_TO_STRING_NAME»(final «type.fullyQualifiedName» obj) {
313         «ENDIF»
314             final «MoreObjects.importedName».ToStringHelper helper = «MoreObjects.importedName».toStringHelper("«type.name»");
315             «FOR property : typeAnalysis.value»
316                 «CODEHELPERS.importedName».appendValue(helper, "«property.name»", obj.«property.getterName»());
317             «ENDFOR»
318             «IF augmentable»
319                 «CODEHELPERS.importedName».appendValue(helper, "«AUGMENTATION_FIELD»", obj.augmentations().values());
320             «ENDIF»
321             return helper.toString();
322         }
323     '''
324
325     def private generateNonnullMethod(MethodSignature method) '''
326         «val ret = method.returnType»
327         «val name = method.name»
328         «formatDataForJavaDoc(method, "@return " + asCode(ret.fullyQualifiedName) + " " + asCode(propertyNameFromGetter(method)) + ", or an empty list if it is not present")»
329         «method.annotations.generateAnnotations»
330         default «ret.importedNonNull» «name»() {
331             return «CODEHELPERS.importedName».nonnull(«name.getGetterMethodForNonnull»());
332         }
333     '''
334
335     def private String nullableType(Type type) {
336         if (type.isObject) {
337             return type.importedNullable
338         }
339         return type.importedName
340     }
341
342     def private static boolean isObject(Type type) {
343         // The return type has a package, so it's not a primitive type
344         return !type.getPackageName().isEmpty()
345     }
346
347     private def boolean analyzeType() {
348         if (typeAnalysis === null) {
349             typeAnalysis = analyzeTypeHierarchy(type)
350         }
351         typeAnalysis.key !== null
352     }
353 }