Merge "bug 741 - Make sure to stop threads on bundle stop on TopologyServiceShim"
[controller.git] / opendaylight / config / yang-jmx-generator-plugin / src / main / java / org / opendaylight / controller / config / yangjmxgenerator / plugin / gofactory / AbsModuleGeneratedObjectFactory.groovy
1 /*
2  * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.gofactory
9 import com.google.common.base.Optional
10 import org.opendaylight.controller.config.api.DependencyResolver
11 import org.opendaylight.controller.config.api.ModuleIdentifier
12 import org.opendaylight.controller.config.api.annotations.Description
13 import org.opendaylight.controller.config.api.runtime.RootRuntimeBeanRegistrator
14 import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry
15 import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.AbstractModuleTemplate
16 import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.TemplateFactory
17 import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Annotation
18 import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.IdentityRefModuleField
19 import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Method
20 import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.ModuleField
21 import org.opendaylight.controller.config.yangjmxgenerator.plugin.java.*
22 import org.opendaylight.yangtools.yang.common.QName
23 import org.slf4j.Logger
24 import org.slf4j.LoggerFactory
25
26 public class AbsModuleGeneratedObjectFactory {
27
28     public GeneratedObject toGeneratedObject(ModuleMXBeanEntry mbe, Optional<String> copyright) {
29         FullyQualifiedName abstractFQN = new FullyQualifiedName(mbe.getPackageName(), mbe.getAbstractModuleName())
30         Optional<String> classJavaDoc = Optional.fromNullable(mbe.getNullableDescription())
31         AbstractModuleTemplate abstractModuleTemplate = TemplateFactory.abstractModuleTemplateFromMbe(mbe)
32         Optional<String> header = abstractModuleTemplate.headerString;
33         List<FullyQualifiedName> implementedInterfaces = abstractModuleTemplate.getTypeDeclaration().getImplemented().collect {
34             FullyQualifiedName.fromString(it)
35         }
36         Optional<FullyQualifiedName> maybeRegistratorType
37         if (abstractModuleTemplate.isRuntime()) {
38             maybeRegistratorType = Optional.of(FullyQualifiedName.fromString(abstractModuleTemplate.getRegistratorType()))
39         } else {
40             maybeRegistratorType = Optional.absent()
41         }
42
43         return toGeneratedObject(abstractFQN, copyright, header, classJavaDoc, implementedInterfaces,
44                 abstractModuleTemplate.getModuleFields(), maybeRegistratorType, abstractModuleTemplate.getMethods(),
45                 mbe.yangModuleQName
46         )
47     }
48
49     public GeneratedObject toGeneratedObject(FullyQualifiedName abstractFQN,
50                                              Optional<String> copyright,
51                                              Optional<String> header,
52                                              Optional<String> classJavaDoc,
53                                              List<FullyQualifiedName> implementedInterfaces,
54                                              List<ModuleField> moduleFields,
55                                              Optional<FullyQualifiedName> maybeRegistratorType,
56                                              List<Method> methods,
57                                              QName yangModuleQName) {
58         JavaFileInputBuilder b = new JavaFileInputBuilder()
59
60         Annotation moduleQNameAnnotation = Annotation.createModuleQNameANnotation(yangModuleQName)
61         b.addClassAnnotation(moduleQNameAnnotation)
62
63         b.setFqn(abstractFQN)
64         b.setTypeName(TypeName.absClassType)
65
66         b.setCopyright(copyright);
67         b.setHeader(header);
68         b.setClassJavaDoc(classJavaDoc);
69         implementedInterfaces.each { b.addImplementsFQN(it) }
70         if (classJavaDoc.isPresent()) {
71             b.addClassAnnotation("@${Description.canonicalName}(value=\"${classJavaDoc.get()}\")")
72         }
73
74         // add logger:
75         b.addToBody(getLogger(abstractFQN));
76
77         b.addToBody("//attributes start");
78
79         b.addToBody(moduleFields.collect { it.toString() }.join("\n"))
80
81         b.addToBody("//attributes end");
82
83
84         b.addToBody(getCommonFields(abstractFQN));
85
86
87         b.addToBody(getNewConstructor(abstractFQN))
88         b.addToBody(getCopyFromOldConstructor(abstractFQN))
89
90         b.addToBody(getRuntimeRegistratorCode(maybeRegistratorType))
91         b.addToBody(getValidationMethods(moduleFields))
92
93         b.addToBody(getCachesOfResolvedDependencies(moduleFields))
94         b.addToBody(getCachesOfResolvedIdentityRefs(moduleFields))
95         b.addToBody(getGetInstance(moduleFields))
96         b.addToBody(getReuseLogic(moduleFields, abstractFQN))
97         b.addToBody(getEqualsAndHashCode(abstractFQN))
98
99         b.addToBody(getMethods(methods))
100
101         return new GeneratedObjectBuilder(b.build()).toGeneratedObject()
102     }
103
104     private static String getMethods(List<Method> methods) {
105         String result = """
106             // getters and setters
107         """
108         result += methods.collect{it.toString()}.join("\n")
109         return result
110     }
111
112     private static String getEqualsAndHashCode(FullyQualifiedName abstractFQN) {
113         return """
114             @Override
115             public boolean equals(Object o) {
116                 if (this == o) return true;
117                 if (o == null || getClass() != o.getClass()) return false;
118                 ${abstractFQN.typeName} that = (${abstractFQN.typeName}) o;
119                 return identifier.equals(that.identifier);
120             }
121
122             @Override
123             public int hashCode() {
124                 return identifier.hashCode();
125             }
126         """
127     }
128
129     private static String getReuseLogic(List<ModuleField> moduleFields, FullyQualifiedName abstractFQN) {
130         String result = """
131             public boolean canReuseInstance(${abstractFQN.typeName} oldModule){
132                 // allow reusing of old instance if no parameters was changed
133                 return isSame(oldModule);
134             }
135
136             public ${AutoCloseable.canonicalName} reuseInstance(${AutoCloseable.canonicalName} oldInstance){
137                 // implement if instance reuse should be supported. Override canReuseInstance to change the criteria.
138                 return oldInstance;
139             }
140             """
141         // isSame method that detects changed fields
142         result += """
143             public boolean isSame(${abstractFQN.typeName} other) {
144                 if (other == null) {
145                     throw new IllegalArgumentException("Parameter 'other' is null");
146                 }
147             """
148         // loop through fields, do deep equals on each field
149         result += moduleFields.collect { field ->
150             if (field.isListOfDependencies()) {
151                 return """
152                     if (${field.name}Dependency.equals(other.${field.name}Dependency) == false) {
153                         return false;
154                     }
155                     for (int idx = 0; idx < ${field.name}Dependency.size(); idx++) {
156                         if (${field.name}Dependency.get(idx) != other.${field.name}Dependency.get(idx)) {
157                             return false;
158                         }
159                     }
160                 """
161             } else if (field.isDependent()) {
162                 return """
163                     if (${field.name}Dependency != other.${field.name}Dependency) { // reference to dependency must be same
164                         return false;
165                     }
166                 """
167             } else {
168                 return """
169                     if (java.util.Objects.deepEquals(${field.name}, other.${field.name}) == false) {
170                         return false;
171                     }
172                 """
173             }
174         }.join("\n")
175
176
177         result += """
178                 return true;
179             }
180             """
181
182         return result
183     }
184
185     private static String getGetInstance(List<ModuleField> moduleFields) {
186         String result = """
187             @Override
188             public final ${AutoCloseable.canonicalName} getInstance() {
189                 if(instance==null) {
190             """
191         // create instance start
192
193         // loop through dependent fields, use dependency resolver to instantiate dependencies. Do it in loop in case field represents list of dependencies.
194         Map<ModuleField, String> resolveDependenciesMap = moduleFields.findAll {
195             it.isDependent()
196         }.collectEntries { ModuleField field ->
197             [field, field.isList() ?
198                     """
199                 ${field.name}Dependency = new java.util.ArrayList<${field.dependency.sie.exportedOsgiClassName}>();
200                 for(javax.management.ObjectName dep : ${field.name}) {
201                     ${field.name}Dependency.add(dependencyResolver.resolveInstance(${
202                         field.dependency.sie.exportedOsgiClassName
203                     }.class, dep, ${field.name}JmxAttribute));
204                 }
205                 """
206                     :
207                     """
208                 ${field.name}Dependency = dependencyResolver.resolveInstance(${
209                         field.dependency.sie.exportedOsgiClassName
210                     }.class, ${field.name}, ${field.name}JmxAttribute);
211                 """
212             ]
213         }
214         // wrap each field resolvation statement with if !=null when dependency is not mandatory
215         def wrapWithNullCheckClosure = {Map<ModuleField, String> map, predicate -> map.collect { ModuleField key, String value ->
216             predicate(key) ? """
217                 if(${key.name}!=null) {
218                     ${value}
219                 }
220                 """ : value
221             }.join("\n")
222         }
223
224         result += wrapWithNullCheckClosure(resolveDependenciesMap, {ModuleField key ->
225             key.getDependency().isMandatory() == false} )
226
227         // add code to inject dependency resolver to fields that support it
228         Map<ModuleField, String> injectDepsMap = moduleFields.findAll { it.needsDepResolver }.collectEntries { field ->
229             if (field.isList()) {
230                 return [field,"""
231                 for(${field.genericInnerType} candidate : ${field.name}) {
232                     candidate.injectDependencyResolver(dependencyResolver);
233                 }
234                 """]
235             } else {
236                 return [field, "${field.name}.injectDependencyResolver(dependencyResolver);"]
237             }
238         }
239
240         result += wrapWithNullCheckClosure(injectDepsMap, {true})
241
242         // identity refs need to be injected with dependencyResolver and base class
243         Map<ModuleField, String> resolveIdentityMap = moduleFields.findAll { it.isIdentityRef() }.collectEntries { IdentityRefModuleField field ->
244             [field,
245             "set${field.attributeName}(${field.name}.resolveIdentity(dependencyResolver, ${field.identityBaseClass}.class));"]
246         }
247
248         result += wrapWithNullCheckClosure(resolveIdentityMap, {true})
249
250         // create instance end: reuse and recreate logic
251         result += """
252                     if(oldInstance!=null && canReuseInstance(oldModule)) {
253                         instance = reuseInstance(oldInstance);
254                     } else {
255                         if(oldInstance!=null) {
256                             try {
257                                 oldInstance.close();
258                             } catch(Exception e) {
259                                 logger.error("An error occurred while closing old instance " + oldInstance, e);
260                             }
261                         }
262                         instance = createInstance();
263                         if (instance == null) {
264                             throw new IllegalStateException("Error in createInstance - null is not allowed as return value");
265                         }
266                     }
267                 }
268                 return instance;
269             }
270             public abstract ${AutoCloseable.canonicalName} createInstance();
271             """
272         return result
273     }
274
275     private static String getCommonFields(FullyQualifiedName abstractFQN) {
276         return """
277             private final ${abstractFQN.typeName} oldModule;
278             private final ${AutoCloseable.canonicalName} oldInstance;
279             private ${AutoCloseable.canonicalName} instance;
280             private final ${DependencyResolver.canonicalName} dependencyResolver;
281             private final ${ModuleIdentifier.canonicalName} identifier;
282             @Override
283             public ${ModuleIdentifier.canonicalName} getIdentifier() {
284                 return identifier;
285             }
286             """
287     }
288
289     private static String getCachesOfResolvedIdentityRefs(List<ModuleField> moduleFields) {
290         return moduleFields.findAll { it.isIdentityRef() }.collect { IdentityRefModuleField field ->
291             "private ${field.identityClassType} ${field.identityClassName};"
292         }.join("\n")
293     }
294
295     private static String getCachesOfResolvedDependencies(List<ModuleField> moduleFields) {
296         return moduleFields.findAll { it.dependent }.collect { field ->
297             if (field.isList()) {
298                 return """
299                     private java.util.List<${field.dependency.sie.exportedOsgiClassName}> ${
300                     field.name
301                 }Dependency = new java.util.ArrayList<${field.dependency.sie.exportedOsgiClassName}>();
302                     protected final java.util.List<${field.dependency.sie.exportedOsgiClassName}> get${
303                     field.attributeName
304                 }Dependency(){
305                         return ${field.name}Dependency;
306                     }
307                     """
308             } else {
309                 return """
310                     private ${field.dependency.sie.exportedOsgiClassName} ${field.name}Dependency;
311                     protected final ${field.dependency.sie.exportedOsgiClassName} get${field.attributeName}Dependency(){
312                         return ${field.name}Dependency;
313                     }
314                     """
315             }
316         }.join("\n")
317     }
318
319     private static String getRuntimeRegistratorCode(Optional<FullyQualifiedName> maybeRegistratorType) {
320         if (maybeRegistratorType.isPresent()) {
321             String registratorType = maybeRegistratorType.get()
322
323             return """
324                 private ${registratorType} rootRuntimeBeanRegistratorWrapper;
325
326                 public ${registratorType} getRootRuntimeBeanRegistratorWrapper(){
327                     return rootRuntimeBeanRegistratorWrapper;
328                 }
329
330                 @Override
331                 public void setRuntimeBeanRegistrator(${RootRuntimeBeanRegistrator.canonicalName} rootRuntimeRegistrator){
332                     this.rootRuntimeBeanRegistratorWrapper = new ${registratorType}(rootRuntimeRegistrator);
333                 }
334                 """
335         } else {
336             return ""
337         }
338     }
339
340     private static String getValidationMethods(List<ModuleField> moduleFields) {
341         String result = """
342             @Override
343             public void validate() {
344             """
345         // validate each mandatory dependency
346         List<String> lines = moduleFields.findAll{(it.dependent && it.dependency.mandatory)}.collect { field ->
347             if (field.isList()) {
348                 return "" +
349                         "for(javax.management.ObjectName dep : ${field.name}) {\n" +
350                         "    dependencyResolver.validateDependency(${field.dependency.sie.fullyQualifiedName}.class, dep, ${field.name}JmxAttribute);\n" +
351                         "}\n"
352             } else {
353                 return "dependencyResolver.validateDependency(${field.dependency.sie.fullyQualifiedName}.class, ${field.name}, ${field.name}JmxAttribute);"
354             }
355         }
356         result += lines.findAll { it.isEmpty() == false }.join("\n")
357         result += """
358                 customValidation();
359             }
360
361             protected void customValidation(){
362             }
363         """
364         return result
365     }
366
367     private static String getLogger(FullyQualifiedName fqn) {
368         return "private static final ${Logger.canonicalName} logger = ${LoggerFactory.canonicalName}.getLogger(${fqn.toString()}.class);"
369     }
370
371     // assumes that each parameter name corresponds to an field in this class, constructs lines setting this.field = field;
372     private static String getConstructorStart(FullyQualifiedName fqn,
373                                               LinkedHashMap<String, String> parameters, String after) {
374         return "public ${fqn.typeName}(" +
375                 parameters.collect { it.key + " " + it.value }.join(",") +
376                 ") {\n" +
377                 parameters.values().collect { "this.${it}=${it};\n" }.join() +
378                 after +
379                 "}\n"
380     }
381
382     private static String getNewConstructor(FullyQualifiedName abstractFQN) {
383         LinkedHashMap<String, String> parameters = [
384                 (ModuleIdentifier.canonicalName): "identifier",
385                 (DependencyResolver.canonicalName): "dependencyResolver"
386         ]
387         String setToNulls = ["oldInstance", "oldModule"].collect { "this.${it}=null;\n" }.join()
388         return getConstructorStart(abstractFQN, parameters, setToNulls)
389     }
390
391     private static String getCopyFromOldConstructor(FullyQualifiedName abstractFQN) {
392         LinkedHashMap<String, String> parameters = [
393                 (ModuleIdentifier.canonicalName): "identifier",
394                 (DependencyResolver.canonicalName): "dependencyResolver",
395                 (abstractFQN.typeName): "oldModule",
396                 (AutoCloseable.canonicalName): "oldInstance"
397         ]
398         return getConstructorStart(abstractFQN, parameters, "")
399     }
400 }