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.util.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.Sets
18 import java.util.ArrayList
19 import java.util.Collection
20 import java.util.HashSet
24 import org.opendaylight.mdsal.binding.model.api.AnnotationType
25 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
26 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
27 import org.opendaylight.mdsal.binding.model.api.GeneratedType
28 import org.opendaylight.mdsal.binding.model.api.JavaTypeName
29 import org.opendaylight.mdsal.binding.model.api.MethodSignature;
30 import org.opendaylight.mdsal.binding.model.api.ParameterizedType
31 import org.opendaylight.mdsal.binding.model.api.Type
32 import org.opendaylight.mdsal.binding.model.util.TypeConstants
33 import org.opendaylight.mdsal.binding.model.util.Types
34 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
35 import org.opendaylight.yangtools.concepts.Builder
36 import com.google.common.collect.ImmutableSet
39 * Template for generating JAVA builder classes.
41 class BuilderTemplate extends AbstractBuilderTemplate {
43 * Constant used as suffix for builder name.
45 package static val BUILDER_STR = "Builder";
47 static val BUILDER = JavaTypeName.create(Builder)
49 val BuilderImplTemplate implTemplate
52 * Constructs new instance of this class.
53 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
55 new(GeneratedType genType, GeneratedType targetType, Type keyType) {
56 super(genType, targetType, keyType)
57 implTemplate = new BuilderImplTemplate(this, type.enclosedTypes.get(0))
60 override isLocalInnerClass(JavaTypeName name) {
61 // Builders do not have inner types
66 * Template method which generates JAVA class body for builder class and for IMPL class.
68 * @return string with JAVA source code
71 «wrapToDocumentation(formatDataForJavaDoc(targetType))»
72 «targetType.annotations.generateDeprecatedAnnotation»
73 public class «type.name» implements «BUILDER.importedName»<«targetType.importedName»> {
75 «generateFields(false)»
77 «constantsDeclarations()»
79 «IF augmentType !== null»
80 «generateAugmentField()»
83 «generateConstructorsFromIfcs()»
85 public «generateCopyConstructor(targetType, type.enclosedTypes.get(0))»
87 «generateMethodFieldsFrom()»
89 «generateGetters(false)»
90 «IF augmentType !== null»
92 «generateAugmentation()»
97 @«OVERRIDE.importedName»
98 public «targetType.name» build() {
99 return new «type.enclosedTypes.get(0).importedName»(this);
106 override generateDeprecatedAnnotation(AnnotationType ann) {
107 val forRemoval = ann.getParameter("forRemoval")
108 if (forRemoval !== null) {
109 return "@" + DEPRECATED.importedName + "(forRemoval = " + forRemoval.value + ")"
111 return "@" + SUPPRESS_WARNINGS.importedName + "(\"deprecation\")"
115 * Generate default constructor and constructor for every implemented interface from uses statements.
117 def private generateConstructorsFromIfcs() '''
118 public «type.name»() {
121 «IF (!(targetType instanceof GeneratedTransferObject))»
122 «FOR impl : targetType.implements SEPARATOR "\n"»
123 «generateConstructorFromIfc(impl)»
129 * Generate constructor with argument of given type.
131 def private Object generateConstructorFromIfc(Type impl) '''
132 «IF (impl instanceof GeneratedType)»
133 «IF impl.hasNonDefaultMethods»
134 public «type.name»(«impl.fullyQualifiedName» arg) {
135 «printConstructorPropertySetter(impl)»
138 «FOR implTypeImplement : impl.implements»
139 «generateConstructorFromIfc(implTypeImplement)»
144 def private Object printConstructorPropertySetter(Type implementedIfc) '''
145 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
146 «val ifc = implementedIfc as GeneratedType»
147 «FOR getter : ifc.nonDefaultMethods»
148 «IF BindingMapping.isGetterMethodName(getter.name)»
149 «val propertyName = getter.propertyNameFromGetter»
150 «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
153 «FOR impl : ifc.implements»
154 «printConstructorPropertySetter(impl, getSpecifiedGetters(ifc))»
159 def private Object printConstructorPropertySetter(Type implementedIfc, Set<MethodSignature> alreadySetProperties) '''
160 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
161 «val ifc = implementedIfc as GeneratedType»
162 «FOR getter : ifc.nonDefaultMethods»
163 «IF BindingMapping.isGetterMethodName(getter.name) && getterByName(alreadySetProperties, getter.name).isEmpty»
164 «val propertyName = getter.propertyNameFromGetter»
165 «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
168 «FOR descendant : ifc.implements»
169 «printConstructorPropertySetter(descendant, Sets.union(alreadySetProperties, getSpecifiedGetters(ifc)))»
174 def static Set<MethodSignature> getSpecifiedGetters(GeneratedType type) {
175 val ImmutableSet.Builder<MethodSignature> setBuilder = new ImmutableSet.Builder
176 for (MethodSignature method : type.getMethodDefinitions()) {
177 if (method.hasOverrideAnnotation) {
178 setBuilder.add(method)
181 return setBuilder.build()
185 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
187 def private generateMethodFieldsFrom() '''
188 «IF (!(targetType instanceof GeneratedTransferObject))»
189 «IF targetType.hasImplementsFromUses»
190 «val List<Type> done = targetType.getBaseIfcs»
191 «generateMethodFieldsFromComment(targetType)»
192 public void fieldsFrom(«DATA_OBJECT.importedName» arg) {
193 boolean isValidArg = false;
194 «FOR impl : targetType.getAllIfcs»
195 «generateIfCheck(impl, done)»
197 «CODEHELPERS.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»");
203 def private generateMethodFieldsFromComment(GeneratedType type) '''
205 * Set fields from given grouping argument. Valid argument is instance of one of following types:
207 «FOR impl : type.getAllIfcs»
208 * <li>«impl.fullyQualifiedName»</li>
212 * @param arg grouping object
213 * @throws IllegalArgumentException if given argument is none of valid types or has property with incompatible value
218 * Method is used to find out if given type implements any interface from uses.
220 def boolean hasImplementsFromUses(GeneratedType type) {
222 for (impl : type.getAllIfcs) {
223 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
230 def private generateIfCheck(Type impl, List<Type> done) '''
231 «IF (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods)»
232 «val implType = impl as GeneratedType»
233 if (arg instanceof «implType.fullyQualifiedName») {
234 «printPropertySetter(implType)»
240 def private printPropertySetter(Type implementedIfc) '''
241 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
242 «val ifc = implementedIfc as GeneratedType»
243 «FOR getter : ifc.nonDefaultMethods»
244 «IF BindingMapping.isGetterMethodName(getter.name) && !hasOverrideAnnotation(getter)»
245 «printPropertySetter(getter, '''((«ifc.fullyQualifiedName»)arg).«getter.name»()''', getter.propertyNameFromGetter)»;
251 def private printPropertySetter(MethodSignature getter, String retrieveProperty, String propertyName) {
252 val ownGetter = implTemplate.findGetter(getter.name)
253 val ownGetterType = ownGetter.returnType
254 if (Types.strictTypeEquals(getter.returnType, ownGetterType)) {
255 return "this._" + propertyName + " = " + retrieveProperty
257 if (Types.isListType(ownGetterType)) {
258 val itemType = (ownGetterType as ParameterizedType).actualTypeArguments.get(0)
260 this._«propertyName» = «CODEHELPERS.importedName».checkListFieldCast(«itemType.fullyQualifiedName».class, "«propertyName»", «retrieveProperty»)'''
263 this._«propertyName» = «CODEHELPERS.importedName».checkFieldCast(«ownGetter.returnType.fullyQualifiedName».class, "«propertyName»", «retrieveProperty»)'''
266 private def List<Type> getBaseIfcs(GeneratedType type) {
267 val List<Type> baseIfcs = new ArrayList();
268 for (ifc : type.implements) {
269 if (ifc instanceof GeneratedType && (ifc as GeneratedType).hasNonDefaultMethods) {
276 private def Set<Type> getAllIfcs(Type type) {
277 val Set<Type> baseIfcs = new HashSet()
278 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
279 val ifc = type as GeneratedType
280 for (impl : ifc.implements) {
281 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
284 baseIfcs.addAll(impl.getAllIfcs)
290 private def List<String> toListOfNames(Collection<Type> types) {
291 val List<String> names = new ArrayList
293 names.add(type.fullyQualifiedName)
298 def private constantsDeclarations() '''
299 «FOR c : type.getConstantDefinitions»
300 «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
301 «val cValue = c.value as Map<String, String>»
302 «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
303 «val jurPatternRef = JUR_PATTERN.importedName»
304 «IF cValue.size == 1»
305 «val firstEntry = cValue.entrySet.iterator.next»
306 private static final «jurPatternRef» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «jurPatternRef».compile("«firstEntry.key.escapeJava»");
307 private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«firstEntry.value.escapeJava»";
309 private static final «jurPatternRef»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CODEHELPERS.importedName».compilePatterns(«ImmutableList.importedName».of(
310 «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
311 private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
312 FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
320 def private generateSetter(GeneratedProperty field) {
321 val returnType = field.returnType
322 if (returnType instanceof ParameterizedType) {
323 if (Types.isListType(returnType)) {
324 return generateListSetter(field, returnType.actualTypeArguments.get(0))
325 } else if (Types.isMapType(returnType)) {
326 return generateMapSetter(field, returnType.actualTypeArguments.get(1))
329 return generateSimpleSetter(field, returnType)
332 def private generateListSetter(GeneratedProperty field, Type actualType) '''
333 «val restrictions = restrictionsForSetter(actualType)»
334 «IF restrictions !== null»
335 «generateCheckers(field, restrictions, actualType)»
337 public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
338 «IF restrictions !== null»
339 if (values != null) {
340 for («actualType.importedName» value : values) {
341 «checkArgument(field, restrictions, actualType, "value")»
345 this.«field.fieldName» = values;
351 // FIXME: MDSAL-540: remove the migration setter
352 def private generateMapSetter(GeneratedProperty field, Type actualType) '''
353 «val restrictions = restrictionsForSetter(actualType)»
354 «val actualTypeRef = actualType.importedName»
355 «val setterName = "set" + field.name.toFirstUpper»
356 «IF restrictions !== null»
357 «generateCheckers(field, restrictions, actualType)»
359 public «type.getName» «setterName»(final «field.returnType.importedName» values) {
360 «IF restrictions !== null»
361 if (values != null) {
362 for («actualTypeRef» value : values.values()) {
363 «checkArgument(field, restrictions, actualType, "value")»
367 this.«field.fieldName» = values;
372 * Utility migration setter.
374 * <b>IMPORTANT NOTE</b>: This method does not completely match previous mechanics, as the list is processed as
375 * during this method's execution. Any future modifications of the list are <b>NOT</b>
376 * reflected in this builder nor its products.
378 * @param values Legacy List of values
379 * @return this builder
380 * @throws IllegalArgumentException if the list contains entries with the same key
381 * @throws NullPointerException if the list contains a null entry
382 * @deprecated Use {#link #«setterName»(«JU_MAP.importedName»)} instead.
384 @«DEPRECATED.importedName»(forRemoval = true)
385 public «type.getName» «setterName»(final «JU_LIST.importedName»<«actualTypeRef»> values) {
386 return «setterName»(«CODEHELPERS.importedName».compatMap(values));
390 def private generateSimpleSetter(GeneratedProperty field, Type actualType) '''
391 «val restrictions = restrictionsForSetter(actualType)»
392 «IF restrictions !== null»
394 «generateCheckers(field, restrictions, actualType)»
397 «val setterName = "set" + field.getName.toFirstUpper»
398 public «type.getName» «setterName»(final «field.returnType.importedName» value) {
399 «IF restrictions !== null»
401 «checkArgument(field, restrictions, actualType, "value")»
404 this.«field.fieldName» = value;
407 «val uintType = UINT_TYPES.get(field.returnType)»
408 «IF uintType !== null»
411 * Utility migration setter.
413 * @param value field value in legacy type
414 * @return this builder
415 * @deprecated Use {#link «setterName»(«field.returnType.importedJavadocName»)} instead.
417 @Deprecated(forRemoval = true)
418 public «type.getName» «setterName»(final «uintType.importedName» value) {
419 return «setterName»(«CODEHELPERS.importedName».compatUint(value));
425 * Template method which generates setter methods
427 * @return string with the setter methods
429 def private generateSetters() '''
430 «IF keyType !== null»
431 public «type.getName» withKey(final «keyType.importedName» key) {
436 «FOR property : properties»
437 «generateSetter(property)»
440 «IF augmentType !== null»
441 «val augmentTypeRef = augmentType.importedName»
442 «val jlClassRef = CLASS.importedName»
443 «val hashMapRef = JU_HASHMAP.importedName»
445 * Add an augmentation to this builder's product.
447 * @param augmentation augmentation to be added
448 * @return this builder
449 * @throws NullPointerException if {@code augmentation} is null
451 public «type.name» addAugmentation(«augmentTypeRef» augmentation) {
452 «jlClassRef»<? extends «augmentTypeRef»> augmentationType = augmentation.«DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME»();
453 if (!(this.«AUGMENTATION_FIELD» instanceof «hashMapRef»)) {
454 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>();
457 this.«AUGMENTATION_FIELD».put(augmentationType, augmentation);
462 * Remove an augmentation from this builder's product. If this builder does not track such an augmentation
463 * type, this method does nothing.
465 * @param augmentationType augmentation type to be removed
466 * @return this builder
468 public «type.name» removeAugmentation(«jlClassRef»<? extends «augmentTypeRef»> augmentationType) {
469 if (this.«AUGMENTATION_FIELD» instanceof «hashMapRef») {
470 this.«AUGMENTATION_FIELD».remove(augmentationType);
477 private def createDescription(GeneratedType targetType) {
478 val target = type.importedName
480 Class that builds {@link «target»} instances. Overall design of the class is that of a
481 <a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
484 In general, this class is supposed to be used like this template:
487 «target» createTarget(int fooXyzzy, int barBaz) {
488 return new «target»Builder()
489 .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
490 .setBar(new BarBuilder().setBaz(barBaz).build())
497 This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
498 worrying about synchronization issues.
501 As a side note: method chaining results in:
503 <li>very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
504 on the stack, so further method invocations just need to fill method arguments for the next method
505 invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
506 <li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
508 <li>better optimization oportunities, as the object scope is minimized in terms of invocation (rather than
509 method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
510 easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
515 @see «BUILDER.importedName»
519 override protected String formatDataForJavaDoc(GeneratedType type) {
520 val typeDescription = createDescription(type)
523 «IF !typeDescription.nullOrEmpty»
529 private def generateAugmentation() '''
530 @«SUPPRESS_WARNINGS.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
531 public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«CLASS.importedName»<E$$> augmentationType) {
532 return (E$$) «AUGMENTATION_FIELD».get(«JU_OBJECTS.importedName».requireNonNull(augmentationType));
536 override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
537 this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
538 «FOR field : keyProps»
539 this.«field.fieldName» = base.«field.getterMethodName»();
543 override protected CharSequence generateCopyNonKeys(Collection<BuilderGeneratedProperty> props) '''
545 this.«field.fieldName» = base.«field.getterName»();
549 override protected generateCopyAugmentation(Type implType) {
550 val hashMapRef = JU_HASHMAP.importedName
551 val augmentTypeRef = augmentType.importedName
553 «JU_MAP.importedName»<«CLASS.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> aug = base.augmentations();
554 if (!aug.isEmpty()) {
555 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug);