2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.mdsal.binding.java.api.generator
10 import static extension org.apache.commons.text.StringEscapeUtils.escapeJava
11 import static org.opendaylight.mdsal.binding.model.ri.BindingTypes.DATA_OBJECT
12 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTABLE_AUGMENTATION_NAME
13 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTATION_FIELD
14 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME
16 import com.google.common.collect.ImmutableList
17 import com.google.common.collect.ImmutableSet
18 import com.google.common.collect.Sets
19 import java.util.ArrayList
20 import java.util.Collection
21 import java.util.HashSet
25 import org.opendaylight.mdsal.binding.model.api.AnnotationType
26 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
27 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
28 import org.opendaylight.mdsal.binding.model.api.GeneratedType
29 import org.opendaylight.mdsal.binding.model.api.JavaTypeName
30 import org.opendaylight.mdsal.binding.model.api.MethodSignature;
31 import org.opendaylight.mdsal.binding.model.api.ParameterizedType
32 import org.opendaylight.mdsal.binding.model.api.Type
33 import org.opendaylight.mdsal.binding.model.ri.TypeConstants
34 import org.opendaylight.mdsal.binding.model.ri.Types
35 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
38 * Template for generating JAVA builder classes.
40 class BuilderTemplate extends AbstractBuilderTemplate {
41 val BuilderImplTemplate implTemplate
44 * Constructs new instance of this class.
45 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
47 new(GeneratedType genType, GeneratedType targetType, Type keyType) {
48 super(genType, targetType, keyType)
49 implTemplate = new BuilderImplTemplate(this, type.enclosedTypes.get(0))
52 override isLocalInnerClass(JavaTypeName name) {
53 // Builders do not have inner types
58 * Template method which generates JAVA class body for builder class and for IMPL class.
60 * @return string with JAVA source code
63 «wrapToDocumentation(formatDataForJavaDoc(targetType))»
64 «targetType.annotations.generateDeprecatedAnnotation»
66 public class «type.name» {
68 «generateFields(false)»
70 «constantsDeclarations()»
72 «IF augmentType !== null»
73 «generateAugmentField()»
76 «generateConstructorsFromIfcs()»
78 public «generateCopyConstructor(targetType, type.enclosedTypes.get(0))»
80 «generateMethodFieldsFrom()»
82 «generateGetters(false)»
83 «IF augmentType !== null»
85 «generateAugmentation()»
91 * A new {@link «targetType.name»} instance.
93 * @return A new {@link «targetType.name»} instance.
95 public «targetType.name» build() {
96 return new «type.enclosedTypes.get(0).importedName»(this);
103 override generateDeprecatedAnnotation(AnnotationType ann) {
104 val forRemoval = ann.getParameter("forRemoval")
105 if (forRemoval !== null) {
106 return "@" + DEPRECATED.importedName + "(forRemoval = " + forRemoval.value + ")"
108 return "@" + SUPPRESS_WARNINGS.importedName + "(\"deprecation\")"
112 * Generate default constructor and constructor for every implemented interface from uses statements.
114 def private generateConstructorsFromIfcs() '''
115 public «type.name»() {
118 «IF (!(targetType instanceof GeneratedTransferObject))»
119 «FOR impl : targetType.implements SEPARATOR "\n"»
120 «generateConstructorFromIfc(impl)»
126 * Generate constructor with argument of given type.
128 def private Object generateConstructorFromIfc(Type impl) '''
129 «IF (impl instanceof GeneratedType)»
130 «IF impl.hasNonDefaultMethods»
131 public «type.name»(«impl.importedName» arg) {
132 «printConstructorPropertySetter(impl)»
135 «FOR implTypeImplement : impl.implements»
136 «generateConstructorFromIfc(implTypeImplement)»
141 def private Object printConstructorPropertySetter(Type implementedIfc) '''
142 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
143 «val ifc = implementedIfc as GeneratedType»
144 «FOR getter : ifc.nonDefaultMethods»
145 «IF BindingMapping.isGetterMethodName(getter.name)»
146 «val propertyName = getter.propertyNameFromGetter»
147 «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
150 «FOR impl : ifc.implements»
151 «printConstructorPropertySetter(impl, getSpecifiedGetters(ifc))»
156 def private Object printConstructorPropertySetter(Type implementedIfc, Set<MethodSignature> alreadySetProperties) '''
157 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
158 «val ifc = implementedIfc as GeneratedType»
159 «FOR getter : ifc.nonDefaultMethods»
160 «IF BindingMapping.isGetterMethodName(getter.name) && getterByName(alreadySetProperties, getter.name).isEmpty»
161 «val propertyName = getter.propertyNameFromGetter»
162 «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
165 «FOR descendant : ifc.implements»
166 «printConstructorPropertySetter(descendant, Sets.union(alreadySetProperties, getSpecifiedGetters(ifc)))»
171 def static Set<MethodSignature> getSpecifiedGetters(GeneratedType type) {
172 val ImmutableSet.Builder<MethodSignature> setBuilder = new ImmutableSet.Builder
173 for (MethodSignature method : type.getMethodDefinitions()) {
174 if (method.hasOverrideAnnotation) {
175 setBuilder.add(method)
178 return setBuilder.build()
182 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
184 def private generateMethodFieldsFrom() '''
185 «IF (!(targetType instanceof GeneratedTransferObject))»
186 «IF targetType.hasImplementsFromUses»
187 «val List<Type> done = targetType.getBaseIfcs»
188 «generateMethodFieldsFromComment(targetType)»
189 public void fieldsFrom(«DATA_OBJECT.importedName» arg) {
190 boolean isValidArg = false;
191 «FOR impl : targetType.getAllIfcs»
192 «generateIfCheck(impl, done)»
194 «CODEHELPERS.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»");
200 def private generateMethodFieldsFromComment(GeneratedType type) '''
202 * Set fields from given grouping argument. Valid argument is instance of one of following types:
204 «FOR impl : type.getAllIfcs»
205 * <li>«impl.importedName»</li>
209 * @param arg grouping object
210 * @throws IllegalArgumentException if given argument is none of valid types or has property with incompatible value
215 * Method is used to find out if given type implements any interface from uses.
217 def boolean hasImplementsFromUses(GeneratedType type) {
219 for (impl : type.getAllIfcs) {
220 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
227 def private generateIfCheck(Type impl, List<Type> done) '''
228 «IF (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods)»
229 «val implType = impl as GeneratedType»
230 if (arg instanceof «implType.importedName») {
231 «printPropertySetter(implType)»
237 def private printPropertySetter(Type implementedIfc) '''
238 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
239 «val ifc = implementedIfc as GeneratedType»
240 «FOR getter : ifc.nonDefaultMethods»
241 «IF BindingMapping.isGetterMethodName(getter.name) && !hasOverrideAnnotation(getter)»
242 «printPropertySetter(getter, '''((«ifc.importedName»)arg).«getter.name»()''', getter.propertyNameFromGetter)»;
248 def private printPropertySetter(MethodSignature getter, String retrieveProperty, String propertyName) {
249 val ownGetter = implTemplate.findGetter(getter.name)
250 val ownGetterType = ownGetter.returnType
251 if (Types.strictTypeEquals(getter.returnType, ownGetterType)) {
252 return "this._" + propertyName + " = " + retrieveProperty
254 if (ownGetterType instanceof ParameterizedType) {
255 val itemType = ownGetterType.actualTypeArguments.get(0)
256 if (Types.isListType(ownGetterType)) {
257 val importedClass = importedClass(itemType)
258 if (importedClass !== null) {
259 return printPropertySetter(retrieveProperty, propertyName, "checkListFieldCastIdentity", importedClass)
261 return printPropertySetter(retrieveProperty, propertyName, "checkListFieldCast", itemType.importedName)
263 if (Types.isSetType(ownGetterType)) {
264 val importedClass = importedClass(itemType)
265 if (importedClass !== null) {
266 return printPropertySetter(retrieveProperty, propertyName, "checkSetFieldCastIdentity", importedClass)
268 return printPropertySetter(retrieveProperty, propertyName, "checkSetFieldCast", itemType.importedName)
270 if (Types.CLASS.equals(ownGetterType)) {
271 return printPropertySetter(retrieveProperty, propertyName, "checkFieldCastIdentity", itemType.identifier.importedName)
274 return printPropertySetter(retrieveProperty, propertyName, "checkFieldCast", ownGetterType.importedName)
277 def private printPropertySetter(String retrieveProperty, String propertyName, String checkerName, String className) '''
278 this._«propertyName» = «CODEHELPERS.importedName».«checkerName»(«className».class, "«propertyName»", «retrieveProperty»)'''
280 private def importedClass(Type type) {
281 if (type instanceof ParameterizedType) {
282 if (Types.CLASS.equals(type.rawType)) {
283 return type.actualTypeArguments.get(0).identifier.importedName
289 private def List<Type> getBaseIfcs(GeneratedType type) {
290 val List<Type> baseIfcs = new ArrayList();
291 for (ifc : type.implements) {
292 if (ifc instanceof GeneratedType && (ifc as GeneratedType).hasNonDefaultMethods) {
299 private def Set<Type> getAllIfcs(Type type) {
300 val Set<Type> baseIfcs = new HashSet()
301 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
302 val ifc = type as GeneratedType
303 for (impl : ifc.implements) {
304 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
307 baseIfcs.addAll(impl.getAllIfcs)
313 private def List<String> toListOfNames(Collection<Type> types) {
314 val List<String> names = new ArrayList
316 names.add(type.importedName)
321 def private constantsDeclarations() '''
322 «FOR c : type.getConstantDefinitions»
323 «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
324 «val cValue = c.value as Map<String, String>»
325 «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
326 «val jurPatternRef = JUR_PATTERN.importedName»
327 «IF cValue.size == 1»
328 «val firstEntry = cValue.entrySet.iterator.next»
329 private static final «jurPatternRef» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «jurPatternRef».compile("«firstEntry.key.escapeJava»");
330 private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«firstEntry.value.escapeJava»";
332 private static final «jurPatternRef»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CODEHELPERS.importedName».compilePatterns(«ImmutableList.importedName».of(
333 «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
334 private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
335 FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
343 def private generateSetter(GeneratedProperty field) {
344 val returnType = field.returnType
345 if (returnType instanceof ParameterizedType) {
346 if (Types.isListType(returnType) || Types.isSetType(returnType)) {
347 val arguments = returnType.actualTypeArguments
348 if (arguments.isEmpty) {
349 return generateListSetter(field, Types.objectType)
351 return generateListSetter(field, arguments.get(0))
352 } else if (Types.isMapType(returnType)) {
353 return generateMapSetter(field, returnType.actualTypeArguments.get(1))
356 return generateSimpleSetter(field, returnType)
359 def private generateListSetter(GeneratedProperty field, Type actualType) '''
360 «val restrictions = restrictionsForSetter(actualType)»
361 «IF restrictions !== null»
362 «generateCheckers(field, restrictions, actualType)»
364 public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
365 «IF restrictions !== null»
366 if (values != null) {
367 for («actualType.importedName» value : values) {
368 «checkArgument(field, restrictions, actualType, "value")»
372 this.«field.fieldName» = values;
378 def private generateMapSetter(GeneratedProperty field, Type actualType) '''
379 «val restrictions = restrictionsForSetter(actualType)»
380 «IF restrictions !== null»
381 «generateCheckers(field, restrictions, actualType)»
383 public «type.getName» set«field.name.toFirstUpper»(final «field.returnType.importedName» values) {
384 «IF restrictions !== null»
385 if (values != null) {
386 for («actualType.importedName» value : values.values()) {
387 «checkArgument(field, restrictions, actualType, "value")»
391 this.«field.fieldName» = values;
396 def private generateSimpleSetter(GeneratedProperty field, Type actualType) '''
397 «val restrictions = restrictionsForSetter(actualType)»
398 «IF restrictions !== null»
400 «generateCheckers(field, restrictions, actualType)»
403 «val setterName = "set" + field.getName.toFirstUpper»
404 public «type.getName» «setterName»(final «field.returnType.importedName» value) {
405 «IF restrictions !== null»
407 «checkArgument(field, restrictions, actualType, "value")»
410 this.«field.fieldName» = value;
416 * Template method which generates setter methods
418 * @return string with the setter methods
420 def private generateSetters() '''
421 «IF keyType !== null»
422 public «type.getName» withKey(final «keyType.importedName» key) {
427 «FOR property : properties»
428 «generateSetter(property)»
431 «IF augmentType !== null»
432 «val augmentTypeRef = augmentType.importedName»
433 «val jlClassRef = CLASS.importedName»
434 «val hashMapRef = JU_HASHMAP.importedName»
436 * Add an augmentation to this builder's product.
438 * @param augmentation augmentation to be added
439 * @return this builder
440 * @throws NullPointerException if {@code augmentation} is null
442 public «type.name» addAugmentation(«augmentTypeRef» augmentation) {
443 «jlClassRef»<? extends «augmentTypeRef»> augmentationType = augmentation.«DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME»();
444 if (!(this.«AUGMENTATION_FIELD» instanceof «hashMapRef»)) {
445 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>();
448 this.«AUGMENTATION_FIELD».put(augmentationType, augmentation);
453 * Remove an augmentation from this builder's product. If this builder does not track such an augmentation
454 * type, this method does nothing.
456 * @param augmentationType augmentation type to be removed
457 * @return this builder
459 public «type.name» removeAugmentation(«jlClassRef»<? extends «augmentTypeRef»> augmentationType) {
460 if (this.«AUGMENTATION_FIELD» instanceof «hashMapRef») {
461 this.«AUGMENTATION_FIELD».remove(augmentationType);
468 private def createDescription(GeneratedType targetType) {
469 val target = targetType.importedName
471 Class that builds {@link «target»} instances. Overall design of the class is that of a
472 <a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
475 In general, this class is supposed to be used like this template:
478 «target» create«target»(int fooXyzzy, int barBaz) {
479 return new «target»Builder()
480 .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
481 .setBar(new BarBuilder().setBaz(barBaz).build())
488 This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
489 worrying about synchronization issues.
492 As a side note: method chaining results in:
494 <li>very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
495 on the stack, so further method invocations just need to fill method arguments for the next method
496 invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
497 <li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
499 <li>better optimization opportunities, as the object scope is minimized in terms of invocation (rather than
500 method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
501 easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
509 override protected String formatDataForJavaDoc(GeneratedType type) {
510 val typeDescription = createDescription(type)
513 «IF !typeDescription.nullOrEmpty»
519 private def generateAugmentation() '''
520 @«SUPPRESS_WARNINGS.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
521 public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«CLASS.importedName»<E$$> augmentationType) {
522 return (E$$) «AUGMENTATION_FIELD».get(«JU_OBJECTS.importedName».requireNonNull(augmentationType));
526 override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
527 this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
528 «FOR field : keyProps»
529 this.«field.fieldName» = base.«field.getterMethodName»();
533 override protected CharSequence generateCopyNonKeys(Collection<BuilderGeneratedProperty> props) '''
535 this.«field.fieldName» = base.«field.getterName»();
539 override protected generateCopyAugmentation(Type implType) {
540 val hashMapRef = JU_HASHMAP.importedName
541 val augmentTypeRef = augmentType.importedName
543 «JU_MAP.importedName»<«CLASS.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> aug = base.augmentations();
544 if (!aug.isEmpty()) {
545 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug);