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 extension org.opendaylight.mdsal.binding.java.api.generator.GeneratorUtil.isNonPresenceContainer;
12 import static org.opendaylight.mdsal.binding.model.ri.BindingTypes.DATA_OBJECT
13 import static org.opendaylight.yangtools.binding.lib.contract.Naming.AUGMENTABLE_AUGMENTATION_NAME
14 import static org.opendaylight.yangtools.binding.lib.contract.Naming.AUGMENTATION_FIELD
15 import static org.opendaylight.yangtools.binding.lib.contract.Naming.BINDING_CONTRACT_IMPLEMENTED_INTERFACE_NAME
16 import static org.opendaylight.yangtools.binding.lib.contract.Naming.KEY_AWARE_KEY_NAME
18 import com.google.common.collect.ImmutableList
19 import com.google.common.collect.ImmutableSet
20 import com.google.common.collect.Sets
21 import java.util.ArrayList
22 import java.util.Collection
23 import java.util.HashSet
27 import org.opendaylight.mdsal.binding.model.api.AnnotationType
28 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
29 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
30 import org.opendaylight.mdsal.binding.model.api.GeneratedType
31 import org.opendaylight.mdsal.binding.model.api.JavaTypeName
32 import org.opendaylight.mdsal.binding.model.api.MethodSignature;
33 import org.opendaylight.mdsal.binding.model.api.ParameterizedType
34 import org.opendaylight.mdsal.binding.model.api.Type
35 import org.opendaylight.mdsal.binding.model.ri.TypeConstants
36 import org.opendaylight.mdsal.binding.model.ri.Types
37 import org.opendaylight.yangtools.binding.lib.contract.Naming
40 * Template for generating JAVA builder classes.
42 class BuilderTemplate extends AbstractBuilderTemplate {
43 val BuilderImplTemplate implTemplate
46 * Constructs new instance of this class.
47 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
49 new(GeneratedType genType, GeneratedType targetType, Type keyType) {
50 super(genType, targetType, keyType)
51 implTemplate = new BuilderImplTemplate(this, type.enclosedTypes.get(0))
54 override isLocalInnerClass(JavaTypeName name) {
55 // Builders do not have inner types
60 * Template method which generates JAVA class body for builder class and for IMPL class.
62 * @return string with JAVA source code
65 «wrapToDocumentation(formatDataForJavaDoc(targetType))»
66 «targetType.annotations.generateDeprecatedAnnotation»
68 public class «type.name» {
70 «generateFields(false)»
72 «constantsDeclarations()»
74 «IF augmentType !== null»
75 «val augmentTypeRef = augmentType.importedName»
76 «val mapTypeRef = JU_MAP.importedName»
77 «mapTypeRef»<«CLASS.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> «AUGMENTATION_FIELD» = «mapTypeRef».of();
81 * Construct an empty builder.
83 public «type.name»() {
87 «generateConstructorsFromIfcs()»
89 «val targetTypeName = targetType.importedName»
91 * Construct a builder initialized with state from specified {@link «targetTypeName»}.
93 * @param base «targetTypeName» from which the builder should be initialized
95 public «generateCopyConstructor(targetType, type.enclosedTypes.get(0))»
97 «generateMethodFieldsFrom()»
99 «IF isNonPresenceContainer(targetType)»
100 «generateEmptyInstance()»
103 «generateGetters(false)»
104 «IF augmentType !== null»
106 «generateAugmentation()»
112 * A new {@link «targetTypeName»} instance.
114 * @return A new {@link «targetTypeName»} instance.
116 public «targetType.importedNonNull» build() {
117 return new «type.enclosedTypes.get(0).importedName»(this);
124 override generateDeprecatedAnnotation(AnnotationType ann) {
125 val forRemoval = ann.getParameter("forRemoval")
126 if (forRemoval !== null) {
127 return "@" + DEPRECATED.importedName + "(forRemoval = " + forRemoval.value + ")"
129 return "@" + SUPPRESS_WARNINGS.importedName + "(\"deprecation\")"
133 * Generate default constructor and constructor for every implemented interface from uses statements.
135 def private generateConstructorsFromIfcs() '''
136 «IF (!(targetType instanceof GeneratedTransferObject))»
137 «FOR impl : targetType.implements SEPARATOR "\n"»
138 «generateConstructorFromIfc(impl)»
144 * Generate constructor with argument of given type.
146 def private Object generateConstructorFromIfc(Type impl) '''
147 «IF (impl instanceof GeneratedType)»
148 «IF impl.hasNonDefaultMethods»
149 «val typeName = impl.importedName»
151 * Construct a new builder initialized from specified {@link «typeName»}.
153 * @param arg «typeName» from which the builder should be initialized
155 public «type.name»(«typeName» arg) {
156 «printConstructorPropertySetter(impl)»
160 «FOR implTypeImplement : impl.implements»
161 «generateConstructorFromIfc(implTypeImplement)»
166 def private Object printConstructorPropertySetter(Type implementedIfc) '''
167 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
168 «val ifc = implementedIfc as GeneratedType»
169 «FOR getter : ifc.nonDefaultMethods»
170 «IF Naming.isGetterMethodName(getter.name)»
171 «val propertyName = getter.propertyNameFromGetter»
172 «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
175 «FOR impl : ifc.implements»
176 «printConstructorPropertySetter(impl, getSpecifiedGetters(ifc))»
181 def private Object printConstructorPropertySetter(Type implementedIfc, Set<MethodSignature> alreadySetProperties) '''
182 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
183 «val ifc = implementedIfc as GeneratedType»
184 «FOR getter : ifc.nonDefaultMethods»
185 «IF Naming.isGetterMethodName(getter.name) && getterByName(alreadySetProperties, getter.name).isEmpty»
186 «val propertyName = getter.propertyNameFromGetter»
187 «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
190 «FOR descendant : ifc.implements»
191 «printConstructorPropertySetter(descendant, Sets.union(alreadySetProperties, getSpecifiedGetters(ifc)))»
196 def static Set<MethodSignature> getSpecifiedGetters(GeneratedType type) {
197 val ImmutableSet.Builder<MethodSignature> setBuilder = new ImmutableSet.Builder
198 for (MethodSignature method : type.getMethodDefinitions()) {
199 if (method.hasOverrideAnnotation) {
200 setBuilder.add(method)
203 return setBuilder.build()
207 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
209 def private generateMethodFieldsFrom() '''
210 «IF (!(targetType instanceof GeneratedTransferObject))»
211 «IF targetType.hasImplementsFromUses»
212 «val List<Type> done = targetType.getBaseIfcs»
213 «generateMethodFieldsFromComment(targetType)»
214 public void fieldsFrom(«DATA_OBJECT.importedName» arg) {
215 boolean isValidArg = false;
216 «FOR impl : targetType.getAllIfcs»
217 «generateIfCheck(impl, done)»
219 «CODEHELPERS.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»");
226 * Generate EMPTY instance which is lazily initialized in empty() method.
228 // TODO: use lazy static field once we have https://openjdk.org/jeps/8209964
229 def private generateEmptyInstance() '''
230 «val nonnullTarget = targetType.importedNonNull»
231 private static final class LazyEmpty {
232 static final «nonnullTarget» INSTANCE = new «type.name»().build();
234 private LazyEmpty() {
240 * Get empty instance of «targetType.name».
242 * @return An empty {@link «targetType.name»}
244 public static «nonnullTarget» empty() {
245 return LazyEmpty.INSTANCE;
249 def private generateMethodFieldsFromComment(GeneratedType type) '''
251 * Set fields from given grouping argument. Valid argument is instance of one of following types:
253 «FOR impl : type.getAllIfcs»
254 * <li>{@link «impl.importedName»}</li>
258 * @param arg grouping object
259 * @throws «IAE.importedName» if given argument is none of valid types or has property with incompatible value
264 * Method is used to find out if given type implements any interface from uses.
266 def boolean hasImplementsFromUses(GeneratedType type) {
268 for (impl : type.getAllIfcs) {
269 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
276 def private generateIfCheck(Type impl, List<Type> done) '''
277 «IF (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods)»
278 «val implType = impl as GeneratedType»
279 if (arg instanceof «implType.importedName» castArg) {
280 «printPropertySetter(implType)»
286 def private printPropertySetter(Type implementedIfc) '''
287 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
288 «val ifc = implementedIfc as GeneratedType»
289 «FOR getter : ifc.nonDefaultMethods»
290 «IF Naming.isGetterMethodName(getter.name) && !hasOverrideAnnotation(getter)»
291 «printPropertySetter(getter, '''castArg.«getter.name»()''', getter.propertyNameFromGetter)»;
297 def private printPropertySetter(MethodSignature getter, String retrieveProperty, String propertyName) {
298 val ownGetter = implTemplate.findGetter(getter.name)
299 val ownGetterType = ownGetter.returnType
300 if (Types.strictTypeEquals(getter.returnType, ownGetterType)) {
301 return "this._" + propertyName + " = " + retrieveProperty
303 if (ownGetterType instanceof ParameterizedType) {
304 val itemType = ownGetterType.actualTypeArguments.get(0)
305 if (Types.isListType(ownGetterType)) {
306 return printPropertySetter(retrieveProperty, propertyName, "checkListFieldCast", itemType.importedName)
308 if (Types.isSetType(ownGetterType)) {
309 return printPropertySetter(retrieveProperty, propertyName, "checkSetFieldCast", itemType.importedName)
312 return printPropertySetter(retrieveProperty, propertyName, "checkFieldCast", ownGetterType.importedName)
315 def private printPropertySetter(String retrieveProperty, String propertyName, String checkerName, String className) '''
316 this._«propertyName» = «CODEHELPERS.importedName».«checkerName»(«className».class, "«propertyName»", «retrieveProperty»)'''
318 private def List<Type> getBaseIfcs(GeneratedType type) {
319 val List<Type> baseIfcs = new ArrayList();
320 for (ifc : type.implements) {
321 if (ifc instanceof GeneratedType && (ifc as GeneratedType).hasNonDefaultMethods) {
328 private def Set<Type> getAllIfcs(Type type) {
329 val Set<Type> baseIfcs = new HashSet()
330 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
331 val ifc = type as GeneratedType
332 for (impl : ifc.implements) {
333 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
336 baseIfcs.addAll(impl.getAllIfcs)
342 private def List<String> toListOfNames(Collection<Type> types) {
343 val List<String> names = new ArrayList
345 names.add(type.importedName)
350 def private constantsDeclarations() '''
351 «FOR c : type.getConstantDefinitions»
352 «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
353 «val cValue = c.value as Map<String, String>»
354 «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
355 «val jurPatternRef = JUR_PATTERN.importedName»
356 «IF cValue.size == 1»
357 «val firstEntry = cValue.entrySet.iterator.next»
358 private static final «jurPatternRef» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «jurPatternRef».compile("«firstEntry.key.escapeJava»");
359 private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«firstEntry.value.escapeJava»";
361 private static final «jurPatternRef»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CODEHELPERS.importedName».compilePatterns(«ImmutableList.importedName».of(
362 «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
363 private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
364 FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
372 def private generateSetter(BuilderGeneratedProperty field) {
373 val returnType = field.returnType
374 if (returnType instanceof ParameterizedType) {
375 if (Types.isListType(returnType) || Types.isSetType(returnType)) {
376 val arguments = returnType.actualTypeArguments
377 if (arguments.isEmpty) {
378 return generateListSetter(field, Types.objectType)
380 return generateListSetter(field, arguments.get(0))
381 } else if (Types.isMapType(returnType)) {
382 return generateMapSetter(field, returnType.actualTypeArguments.get(1))
385 return generateSimpleSetter(field, returnType)
388 def private generateListSetter(BuilderGeneratedProperty field, Type actualType) '''
389 «val restrictions = restrictionsForSetter(actualType)»
390 «IF restrictions !== null»
391 «generateCheckers(field, restrictions, actualType)»
395 * Set the property corresponding to {@link «targetType.importedName»#«field.getterName»()} to the specified
398 * @param values desired value
399 * @return this builder
401 public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
402 «IF restrictions !== null»
403 if (values != null) {
404 for («actualType.importedName» value : values) {
405 «checkArgument(field, restrictions, actualType, "value")»
409 this.«field.fieldName» = values;
415 def private generateMapSetter(BuilderGeneratedProperty field, Type actualType) '''
416 «val restrictions = restrictionsForSetter(actualType)»
417 «IF restrictions !== null»
418 «generateCheckers(field, restrictions, actualType)»
422 * Set the property corresponding to {@link «targetType.importedName»#«field.getterName»()} to the specified
425 * @param values desired value
426 * @return this builder
428 public «type.getName» set«field.name.toFirstUpper»(final «field.returnType.importedName» values) {
429 «IF restrictions !== null»
430 if (values != null) {
431 for («actualType.importedName» value : values.values()) {
432 «checkArgument(field, restrictions, actualType, "value")»
436 this.«field.fieldName» = values;
441 def private generateSimpleSetter(BuilderGeneratedProperty field, Type actualType) '''
442 «val restrictions = restrictionsForSetter(actualType)»
443 «IF restrictions !== null»
445 «generateCheckers(field, restrictions, actualType)»
449 * Set the property corresponding to {@link «targetType.importedName»#«field.getterName»()} to the specified
452 * @param value desired value
453 * @return this builder
455 «val setterName = "set" + field.getName.toFirstUpper»
456 public «type.getName» «setterName»(final «field.returnType.importedName» value) {
457 «IF restrictions !== null»
459 «checkArgument(field, restrictions, actualType, "value")»
462 this.«field.fieldName» = value;
468 * Template method which generates setter methods
470 * @return string with the setter methods
472 def private generateSetters() '''
473 «IF keyType !== null»
475 * Set the key value corresponding to {@link «targetType.importedName»#«KEY_AWARE_KEY_NAME»()} to the specified
478 * @param key desired value
479 * @return this builder
481 public «type.getName» withKey(final «keyType.importedName» key) {
486 «FOR property : properties»
487 «generateSetter(property)»
490 «IF augmentType !== null»
491 «val augmentTypeRef = augmentType.importedName»
492 «val hashMapRef = JU_HASHMAP.importedName»
494 * Add an augmentation to this builder's product.
496 * @param augmentation augmentation to be added
497 * @return this builder
498 * @throws «NPE.importedName» if {@code augmentation} is null
500 public «type.name» addAugmentation(«augmentTypeRef» augmentation) {
501 if (!(this.«AUGMENTATION_FIELD» instanceof «hashMapRef»)) {
502 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>();
505 this.«AUGMENTATION_FIELD».put(augmentation.«BINDING_CONTRACT_IMPLEMENTED_INTERFACE_NAME»(), augmentation);
510 * Remove an augmentation from this builder's product. If this builder does not track such an augmentation
511 * type, this method does nothing.
513 * @param augmentationType augmentation type to be removed
514 * @return this builder
516 public «type.name» removeAugmentation(«CLASS.importedName»<? extends «augmentTypeRef»> augmentationType) {
517 if (this.«AUGMENTATION_FIELD» instanceof «hashMapRef») {
518 this.«AUGMENTATION_FIELD».remove(augmentationType);
525 private def createDescription(GeneratedType targetType) {
526 val target = targetType.importedName
528 Class that builds {@link «target»} instances. Overall design of the class is that of a
529 <a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
532 In general, this class is supposed to be used like this template:
535 «target» create«target»(int fooXyzzy, int barBaz) {
536 return new «target»Builder()
537 .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
538 .setBar(new BarBuilder().setBaz(barBaz).build())
545 This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
546 worrying about synchronization issues.
549 As a side note: method chaining results in:
551 <li>very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
552 on the stack, so further method invocations just need to fill method arguments for the next method
553 invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
554 <li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
556 <li>better optimization opportunities, as the object scope is minimized in terms of invocation (rather than
557 method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
558 easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
566 override protected String formatDataForJavaDoc(GeneratedType type) {
567 val typeDescription = createDescription(type)
570 «IF !typeDescription.nullOrEmpty»
576 private def generateAugmentation() '''
578 * Return the specified augmentation, if it is present in this builder.
580 * @param <E$$> augmentation type
581 * @param augmentationType augmentation type class
582 * @return Augmentation object from this builder, or {@code null} if not present
583 * @throws «NPE.importedName» if {@code augmentType} is {@code null}
585 @«SUPPRESS_WARNINGS.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
586 public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«CLASS.importedName»<E$$> augmentationType) {
587 return (E$$) «AUGMENTATION_FIELD».get(«JU_OBJECTS.importedName».requireNonNull(augmentationType));
591 override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
592 this.key = base.«KEY_AWARE_KEY_NAME»();
593 «FOR field : keyProps»
594 this.«field.fieldName» = base.«field.getterMethodName»();
598 override protected CharSequence generateCopyNonKeys(Collection<BuilderGeneratedProperty> props) '''
600 this.«field.fieldName» = base.«field.getterName»();
604 override protected generateCopyAugmentation(Type implType) '''
605 final var aug = base.augmentations();
606 if (!aug.isEmpty()) {
607 this.«AUGMENTATION_FIELD» = new «JU_HASHMAP.importedName»<>(aug);