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