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()»
77 * Construct an empty builder.
79 public «type.name»() {
83 «generateConstructorsFromIfcs()»
85 «val targetTypeName = targetType.importedName»
87 * Construct a builder initialized with state from specified {@link «targetTypeName»}.
89 * @param base «targetTypeName» from which the builder should be initialized
91 public «generateCopyConstructor(targetType, type.enclosedTypes.get(0))»
93 «generateMethodFieldsFrom()»
95 «generateGetters(false)»
96 «IF augmentType !== null»
98 «generateAugmentation()»
104 * A new {@link «targetTypeName»} instance.
106 * @return A new {@link «targetTypeName»} instance.
108 public «targetTypeName» build() {
109 return new «type.enclosedTypes.get(0).importedName»(this);
116 override generateDeprecatedAnnotation(AnnotationType ann) {
117 val forRemoval = ann.getParameter("forRemoval")
118 if (forRemoval !== null) {
119 return "@" + DEPRECATED.importedName + "(forRemoval = " + forRemoval.value + ")"
121 return "@" + SUPPRESS_WARNINGS.importedName + "(\"deprecation\")"
125 * Generate default constructor and constructor for every implemented interface from uses statements.
127 def private generateConstructorsFromIfcs() '''
128 «IF (!(targetType instanceof GeneratedTransferObject))»
129 «FOR impl : targetType.implements SEPARATOR "\n"»
130 «generateConstructorFromIfc(impl)»
136 * Generate constructor with argument of given type.
138 def private Object generateConstructorFromIfc(Type impl) '''
139 «IF (impl instanceof GeneratedType)»
140 «IF impl.hasNonDefaultMethods»
141 «val typeName = impl.importedName»
143 * Construct a new builder initialized from specified {@link «typeName»}.
145 * @param arg «typeName» from which the builder should be initialized
147 public «type.name»(«typeName» arg) {
148 «printConstructorPropertySetter(impl)»
152 «FOR implTypeImplement : impl.implements»
153 «generateConstructorFromIfc(implTypeImplement)»
158 def private Object printConstructorPropertySetter(Type implementedIfc) '''
159 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
160 «val ifc = implementedIfc as GeneratedType»
161 «FOR getter : ifc.nonDefaultMethods»
162 «IF BindingMapping.isGetterMethodName(getter.name)»
163 «val propertyName = getter.propertyNameFromGetter»
164 «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
167 «FOR impl : ifc.implements»
168 «printConstructorPropertySetter(impl, getSpecifiedGetters(ifc))»
173 def private Object printConstructorPropertySetter(Type implementedIfc, Set<MethodSignature> alreadySetProperties) '''
174 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
175 «val ifc = implementedIfc as GeneratedType»
176 «FOR getter : ifc.nonDefaultMethods»
177 «IF BindingMapping.isGetterMethodName(getter.name) && getterByName(alreadySetProperties, getter.name).isEmpty»
178 «val propertyName = getter.propertyNameFromGetter»
179 «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
182 «FOR descendant : ifc.implements»
183 «printConstructorPropertySetter(descendant, Sets.union(alreadySetProperties, getSpecifiedGetters(ifc)))»
188 def static Set<MethodSignature> getSpecifiedGetters(GeneratedType type) {
189 val ImmutableSet.Builder<MethodSignature> setBuilder = new ImmutableSet.Builder
190 for (MethodSignature method : type.getMethodDefinitions()) {
191 if (method.hasOverrideAnnotation) {
192 setBuilder.add(method)
195 return setBuilder.build()
199 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
201 def private generateMethodFieldsFrom() '''
202 «IF (!(targetType instanceof GeneratedTransferObject))»
203 «IF targetType.hasImplementsFromUses»
204 «val List<Type> done = targetType.getBaseIfcs»
205 «generateMethodFieldsFromComment(targetType)»
206 public void fieldsFrom(«DATA_OBJECT.importedName» arg) {
207 boolean isValidArg = false;
208 «FOR impl : targetType.getAllIfcs»
209 «generateIfCheck(impl, done)»
211 «CODEHELPERS.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»");
217 def private generateMethodFieldsFromComment(GeneratedType type) '''
219 * Set fields from given grouping argument. Valid argument is instance of one of following types:
221 «FOR impl : type.getAllIfcs»
222 * <li>{@link «impl.importedName»}</li>
226 * @param arg grouping object
227 * @throws IllegalArgumentException if given argument is none of valid types or has property with incompatible value
232 * Method is used to find out if given type implements any interface from uses.
234 def boolean hasImplementsFromUses(GeneratedType type) {
236 for (impl : type.getAllIfcs) {
237 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
244 def private generateIfCheck(Type impl, List<Type> done) '''
245 «IF (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods)»
246 «val implType = impl as GeneratedType»
247 if (arg instanceof «implType.importedName») {
248 «printPropertySetter(implType)»
254 def private printPropertySetter(Type implementedIfc) '''
255 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
256 «val ifc = implementedIfc as GeneratedType»
257 «FOR getter : ifc.nonDefaultMethods»
258 «IF BindingMapping.isGetterMethodName(getter.name) && !hasOverrideAnnotation(getter)»
259 «printPropertySetter(getter, '''((«ifc.importedName»)arg).«getter.name»()''', getter.propertyNameFromGetter)»;
265 def private printPropertySetter(MethodSignature getter, String retrieveProperty, String propertyName) {
266 val ownGetter = implTemplate.findGetter(getter.name)
267 val ownGetterType = ownGetter.returnType
268 if (Types.strictTypeEquals(getter.returnType, ownGetterType)) {
269 return "this._" + propertyName + " = " + retrieveProperty
271 if (ownGetterType instanceof ParameterizedType) {
272 val itemType = ownGetterType.actualTypeArguments.get(0)
273 if (Types.isListType(ownGetterType)) {
274 val importedClass = importedClass(itemType)
275 if (importedClass !== null) {
276 return printPropertySetter(retrieveProperty, propertyName, "checkListFieldCastIdentity", importedClass)
278 return printPropertySetter(retrieveProperty, propertyName, "checkListFieldCast", itemType.importedName)
280 if (Types.isSetType(ownGetterType)) {
281 val importedClass = importedClass(itemType)
282 if (importedClass !== null) {
283 return printPropertySetter(retrieveProperty, propertyName, "checkSetFieldCastIdentity", importedClass)
285 return printPropertySetter(retrieveProperty, propertyName, "checkSetFieldCast", itemType.importedName)
287 if (Types.CLASS.equals(ownGetterType)) {
288 return printPropertySetter(retrieveProperty, propertyName, "checkFieldCastIdentity", itemType.identifier.importedName)
291 return printPropertySetter(retrieveProperty, propertyName, "checkFieldCast", ownGetterType.importedName)
294 def private printPropertySetter(String retrieveProperty, String propertyName, String checkerName, String className) '''
295 this._«propertyName» = «CODEHELPERS.importedName».«checkerName»(«className».class, "«propertyName»", «retrieveProperty»)'''
297 private def importedClass(Type type) {
298 if (type instanceof ParameterizedType) {
299 if (Types.CLASS.equals(type.rawType)) {
300 return type.actualTypeArguments.get(0).identifier.importedName
306 private def List<Type> getBaseIfcs(GeneratedType type) {
307 val List<Type> baseIfcs = new ArrayList();
308 for (ifc : type.implements) {
309 if (ifc instanceof GeneratedType && (ifc as GeneratedType).hasNonDefaultMethods) {
316 private def Set<Type> getAllIfcs(Type type) {
317 val Set<Type> baseIfcs = new HashSet()
318 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
319 val ifc = type as GeneratedType
320 for (impl : ifc.implements) {
321 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
324 baseIfcs.addAll(impl.getAllIfcs)
330 private def List<String> toListOfNames(Collection<Type> types) {
331 val List<String> names = new ArrayList
333 names.add(type.importedName)
338 def private constantsDeclarations() '''
339 «FOR c : type.getConstantDefinitions»
340 «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
341 «val cValue = c.value as Map<String, String>»
342 «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
343 «val jurPatternRef = JUR_PATTERN.importedName»
344 «IF cValue.size == 1»
345 «val firstEntry = cValue.entrySet.iterator.next»
346 private static final «jurPatternRef» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «jurPatternRef».compile("«firstEntry.key.escapeJava»");
347 private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«firstEntry.value.escapeJava»";
349 private static final «jurPatternRef»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CODEHELPERS.importedName».compilePatterns(«ImmutableList.importedName».of(
350 «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
351 private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
352 FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
360 def private generateSetter(GeneratedProperty field) {
361 val returnType = field.returnType
362 if (returnType instanceof ParameterizedType) {
363 if (Types.isListType(returnType) || Types.isSetType(returnType)) {
364 val arguments = returnType.actualTypeArguments
365 if (arguments.isEmpty) {
366 return generateListSetter(field, Types.objectType)
368 return generateListSetter(field, arguments.get(0))
369 } else if (Types.isMapType(returnType)) {
370 return generateMapSetter(field, returnType.actualTypeArguments.get(1))
373 return generateSimpleSetter(field, returnType)
376 def private generateListSetter(GeneratedProperty field, Type actualType) '''
377 «val restrictions = restrictionsForSetter(actualType)»
378 «IF restrictions !== null»
379 «generateCheckers(field, restrictions, actualType)»
381 public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
382 «IF restrictions !== null»
383 if (values != null) {
384 for («actualType.importedName» value : values) {
385 «checkArgument(field, restrictions, actualType, "value")»
389 this.«field.fieldName» = values;
395 def private generateMapSetter(GeneratedProperty field, Type actualType) '''
396 «val restrictions = restrictionsForSetter(actualType)»
397 «IF restrictions !== null»
398 «generateCheckers(field, restrictions, actualType)»
400 public «type.getName» set«field.name.toFirstUpper»(final «field.returnType.importedName» values) {
401 «IF restrictions !== null»
402 if (values != null) {
403 for («actualType.importedName» value : values.values()) {
404 «checkArgument(field, restrictions, actualType, "value")»
408 this.«field.fieldName» = values;
413 def private generateSimpleSetter(GeneratedProperty field, Type actualType) '''
414 «val restrictions = restrictionsForSetter(actualType)»
415 «IF restrictions !== null»
417 «generateCheckers(field, restrictions, actualType)»
420 «val setterName = "set" + field.getName.toFirstUpper»
421 public «type.getName» «setterName»(final «field.returnType.importedName» value) {
422 «IF restrictions !== null»
424 «checkArgument(field, restrictions, actualType, "value")»
427 this.«field.fieldName» = value;
433 * Template method which generates setter methods
435 * @return string with the setter methods
437 def private generateSetters() '''
438 «IF keyType !== null»
439 public «type.getName» withKey(final «keyType.importedName» key) {
444 «FOR property : properties»
445 «generateSetter(property)»
448 «IF augmentType !== null»
449 «val augmentTypeRef = augmentType.importedName»
450 «val jlClassRef = CLASS.importedName»
451 «val hashMapRef = JU_HASHMAP.importedName»
453 * Add an augmentation to this builder's product.
455 * @param augmentation augmentation to be added
456 * @return this builder
457 * @throws NullPointerException if {@code augmentation} is null
459 public «type.name» addAugmentation(«augmentTypeRef» augmentation) {
460 «jlClassRef»<? extends «augmentTypeRef»> augmentationType = augmentation.«DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME»();
461 if (!(this.«AUGMENTATION_FIELD» instanceof «hashMapRef»)) {
462 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>();
465 this.«AUGMENTATION_FIELD».put(augmentationType, augmentation);
470 * Remove an augmentation from this builder's product. If this builder does not track such an augmentation
471 * type, this method does nothing.
473 * @param augmentationType augmentation type to be removed
474 * @return this builder
476 public «type.name» removeAugmentation(«jlClassRef»<? extends «augmentTypeRef»> augmentationType) {
477 if (this.«AUGMENTATION_FIELD» instanceof «hashMapRef») {
478 this.«AUGMENTATION_FIELD».remove(augmentationType);
485 private def createDescription(GeneratedType targetType) {
486 val target = targetType.importedName
488 Class that builds {@link «target»} instances. Overall design of the class is that of a
489 <a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
492 In general, this class is supposed to be used like this template:
495 «target» create«target»(int fooXyzzy, int barBaz) {
496 return new «target»Builder()
497 .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
498 .setBar(new BarBuilder().setBaz(barBaz).build())
505 This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
506 worrying about synchronization issues.
509 As a side note: method chaining results in:
511 <li>very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
512 on the stack, so further method invocations just need to fill method arguments for the next method
513 invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
514 <li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
516 <li>better optimization opportunities, as the object scope is minimized in terms of invocation (rather than
517 method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
518 easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
526 override protected String formatDataForJavaDoc(GeneratedType type) {
527 val typeDescription = createDescription(type)
530 «IF !typeDescription.nullOrEmpty»
536 private def generateAugmentation() '''
537 @«SUPPRESS_WARNINGS.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
538 public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«CLASS.importedName»<E$$> augmentationType) {
539 return (E$$) «AUGMENTATION_FIELD».get(«JU_OBJECTS.importedName».requireNonNull(augmentationType));
543 override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
544 this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
545 «FOR field : keyProps»
546 this.«field.fieldName» = base.«field.getterMethodName»();
550 override protected CharSequence generateCopyNonKeys(Collection<BuilderGeneratedProperty> props) '''
552 this.«field.fieldName» = base.«field.getterName»();
556 override protected generateCopyAugmentation(Type implType) '''
557 final var aug = base.augmentations();
558 if (!aug.isEmpty()) {
559 this.«AUGMENTATION_FIELD» = new «JU_HASHMAP.importedName»<>(aug);