c6840aa3ccc9adcf95be0f58cf956bd506c56c20
[mdsal.git] / binding / mdsal-binding-java-api-generator / src / main / java / org / opendaylight / mdsal / binding / java / api / generator / YangModuleInfoTemplate.xtend
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.mdsal.binding.java.api.generator
9
10 import static extension org.opendaylight.mdsal.binding.spec.naming.BindingMapping.getClassName
11 import static extension org.opendaylight.mdsal.binding.spec.naming.BindingMapping.getRootPackageName
12 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.MODEL_BINDING_PROVIDER_CLASS_NAME
13 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.MODULE_INFO_CLASS_NAME
14 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.MODULE_INFO_QNAMEOF_METHOD_NAME
15
16 import com.google.common.base.Preconditions
17 import com.google.common.collect.ImmutableSet
18 import java.util.Comparator
19 import java.util.HashSet
20 import java.util.Optional
21 import java.util.Set
22 import java.util.TreeMap
23 import java.util.function.Function
24 import org.eclipse.xtend.lib.annotations.Accessors
25 import org.opendaylight.yangtools.yang.binding.YangModuleInfo
26 import org.opendaylight.yangtools.yang.common.Revision
27 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext
28 import org.opendaylight.yangtools.yang.model.api.Module
29 import org.opendaylight.yangtools.yang.model.api.ModuleLike
30 import org.opendaylight.yangtools.yang.model.api.Submodule
31
32 /**
33  * Template for {@link YangModuleInfo} implementation for a particular module. Aside from fulfilling that contract,
34  * this class provides a static {@code createQName(String)} method, which is used by co-generated code to initialize
35  * QNAME constants.
36  */
37 final class YangModuleInfoTemplate {
38     static val Comparator<Optional<Revision>> REVISION_COMPARATOR =
39         [ Optional<Revision> first, Optional<Revision> second | Revision.compare(first, second) ]
40
41     // These are always imported. Note we need to import even java.lang members, as there can be conflicting definitions
42     // in our package
43     static val CORE_IMPORT_STR = '''
44         import com.google.common.collect.ImmutableSet;
45         import java.lang.Override;
46         import java.lang.String;
47         import org.eclipse.jdt.annotation.NonNull;
48         import org.opendaylight.yangtools.yang.binding.ResourceYangModuleInfo;
49         import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
50         import org.opendaylight.yangtools.yang.common.QName;
51     '''
52     static val EXT_IMPORT_STR = '''
53         import com.google.common.collect.ImmutableSet;
54         import java.lang.Override;
55         import java.lang.String;
56         import java.util.HashSet;
57         import java.util.Set;
58         import org.eclipse.jdt.annotation.NonNull;
59         import org.opendaylight.yangtools.yang.binding.ResourceYangModuleInfo;
60         import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
61         import org.opendaylight.yangtools.yang.common.QName;
62     '''
63
64     val Module module
65     val EffectiveModelContext ctx
66     val Function<ModuleLike, Optional<String>> moduleFilePathResolver
67
68     var importedTypes = CORE_IMPORT_STR
69
70     @Accessors
71     val String packageName
72
73     @Accessors
74     val String modelBindingProviderName
75
76     new(Module module, EffectiveModelContext ctx, Function<ModuleLike, Optional<String>> moduleFilePathResolver) {
77         Preconditions.checkArgument(module !== null, "Module must not be null.")
78         this.module = module
79         this.ctx = ctx
80         this.moduleFilePathResolver = moduleFilePathResolver
81         packageName = module.QNameModule.rootPackageName;
82         modelBindingProviderName = '''«packageName».«MODEL_BINDING_PROVIDER_CLASS_NAME»'''
83     }
84
85     def String generate() {
86         val Set<Submodule> submodules = new HashSet
87         collectSubmodules(submodules, module)
88
89         val body = '''
90             /**
91              * The {@link ResourceYangModuleInfo} for {@code «module.name»} module.
92              */
93             @«JavaFileTemplate.GENERATED»("mdsal-binding-generator")
94             public final class «MODULE_INFO_CLASS_NAME» extends ResourceYangModuleInfo {
95                 «val rev = module.revision»
96                 private static final @NonNull QName NAME = QName.create("«module.QNameModule.namespace.toString»", «IF rev.present»"«rev.get.toString»", «ENDIF»"«module.name»").intern();
97                 private static final @NonNull YangModuleInfo INSTANCE = new «MODULE_INFO_CLASS_NAME»();
98
99                 private final @NonNull ImmutableSet<YangModuleInfo> importedModules;
100
101                 /**
102                  * Return the singleton instance of this class.
103                  *
104                  * @return The singleton instance
105                  */
106                 public static @NonNull YangModuleInfo getInstance() {
107                     return INSTANCE;
108                 }
109
110                 /**
111                  * Create an interned {@link QName} with specified {@code localName} and namespace/revision of this
112                  * module.
113                  *
114                  * @param localName local name
115                  * @return A QName
116                  * @throws NullPointerException if {@code localName} is null
117                  * @throws IllegalArgumentException if localName is not a valid YANG identifier
118                  */
119                 public static @NonNull QName «MODULE_INFO_QNAMEOF_METHOD_NAME»(final String localName) {
120                     return QName.create(NAME, localName).intern();
121                 }
122
123                 «classBody(module, MODULE_INFO_CLASS_NAME, submodules)»
124             }
125         '''
126         return '''
127             package «packageName»;
128
129             «importedTypes»
130
131             «body»
132         '''.toString
133     }
134
135     def String generateModelProvider() '''
136         package «packageName»;
137
138         import java.lang.Override;
139         import java.util.ServiceLoader;
140         import org.opendaylight.yangtools.yang.binding.YangModelBindingProvider;
141         import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
142
143         /**
144          * The {@link YangModelBindingProvider} for {@code «module.name»} module. This class should not be used
145          * directly, but rather through {@link ServiceLoader}.
146          */
147         @«JavaFileTemplate.GENERATED»("mdsal-binding-generator")
148         public final class «MODEL_BINDING_PROVIDER_CLASS_NAME» implements YangModelBindingProvider {
149             /**
150              * Construct a new provider.
151              */
152             public «MODEL_BINDING_PROVIDER_CLASS_NAME»() {
153                 // No-op
154             }
155
156             @Override
157             public YangModuleInfo getModuleInfo() {
158                 return «MODULE_INFO_CLASS_NAME».getInstance();
159             }
160         }
161     '''
162
163     private static def void collectSubmodules(Set<Submodule> dest, ModuleLike module) {
164         for (Submodule submodule : module.submodules) {
165             if (dest.add(submodule)) {
166                 collectSubmodules(dest, submodule)
167             }
168         }
169     }
170
171     private def CharSequence classBody(ModuleLike m, String className, Set<Submodule> submodules) '''
172         private «className»() {
173             «IF !m.imports.empty || !submodules.empty»
174                 «extendImports»
175                 Set<YangModuleInfo> set = new HashSet<>();
176             «ENDIF»
177             «IF !m.imports.empty»
178                 «FOR imp : m.imports»
179                     «val name = imp.moduleName.localName»
180                     «val rev = imp.revision»
181                     «IF !rev.present»
182                         «val TreeMap<Optional<Revision>, Module> sorted = new TreeMap(REVISION_COMPARATOR)»
183                         «FOR module : ctx.modules»
184                             «IF name.equals(module.name)»
185                                 «sorted.put(module.revision, module)»
186                             «ENDIF»
187                         «ENDFOR»
188                         set.add(«sorted.lastEntry().value.QNameModule.rootPackageName».«MODULE_INFO_CLASS_NAME».getInstance());
189                     «ELSE»
190                         set.add(«(ctx.findModule(name, rev).orElseThrow.QNameModule).rootPackageName».«MODULE_INFO_CLASS_NAME».getInstance());
191                     «ENDIF»
192                 «ENDFOR»
193             «ENDIF»
194             «FOR submodule : submodules»
195                 set.add(«submodule.name.className»Info.getInstance());
196             «ENDFOR»
197             «IF m.imports.empty && submodules.empty»
198                 importedModules = ImmutableSet.of();
199             «ELSE»
200                 importedModules = ImmutableSet.copyOf(set);
201             «ENDIF»
202         }
203
204         @Override
205         public QName getName() {
206             return NAME;
207         }
208
209         @Override
210         protected String resourceName() {
211             return "«sourcePath(m)»";
212         }
213
214         @Override
215         public ImmutableSet<YangModuleInfo> getImportedModules() {
216             return importedModules;
217         }
218         «generateSubInfo(submodules)»
219     '''
220
221     private def void extendImports() {
222         importedTypes = EXT_IMPORT_STR
223     }
224
225     private def sourcePath(ModuleLike module) {
226         val opt = moduleFilePathResolver.apply(module)
227         Preconditions.checkState(opt.isPresent, "Module %s does not have a file path", module)
228         return opt.get
229     }
230
231     private def generateSubInfo(Set<Submodule> submodules) '''
232         «FOR submodule : submodules»
233             «val className = submodule.name.className»
234
235             private static final class «className»Info extends ResourceYangModuleInfo {
236                 «val rev = submodule.revision»
237                 private final @NonNull QName NAME = QName.create("«submodule.QNameModule.namespace.toString»", «
238                 IF rev.present»"«rev.get.toString»", «ENDIF»"«submodule.name»").intern();
239                 private static final @NonNull YangModuleInfo INSTANCE = new «className»Info();
240
241                 private final @NonNull ImmutableSet<YangModuleInfo> importedModules;
242
243                 public static @NonNull YangModuleInfo getInstance() {
244                     return INSTANCE;
245                 }
246
247                 «classBody(submodule, className + "Info", ImmutableSet.of)»
248             }
249         «ENDFOR»
250     '''
251 }