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