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